@@ -2679,6 +2679,124 @@ def fake_popen(cmd, **kwargs):
26792679 assert env .get ("HERMES_PROFILE" ) == "some-profile"
26802680
26812681
2682+ def test_default_spawn_raises_terminal_timeout_to_task_runtime (kanban_home , monkeypatch ):
2683+ """A task runtime cap should raise the worker's terminal default.
2684+
2685+ This is worker-scoped env only: normal CLI/gateway terminal settings stay
2686+ untouched, but long kanban tasks no longer inherit a short generic
2687+ TERMINAL_TIMEOUT that kills their foreground command first.
2688+ """
2689+ captured = {}
2690+
2691+ class FakeProc :
2692+ pid = 123
2693+
2694+ def fake_popen (cmd , ** kwargs ):
2695+ captured ["env" ] = kwargs .get ("env" , {})
2696+ return FakeProc ()
2697+
2698+ monkeypatch .setattr ("subprocess.Popen" , fake_popen )
2699+ monkeypatch .setenv ("TERMINAL_TIMEOUT" , "180" )
2700+ monkeypatch .delenv ("TERMINAL_MAX_FOREGROUND_TIMEOUT" , raising = False )
2701+
2702+ conn = kb .connect ()
2703+ try :
2704+ tid = kb .create_task (
2705+ conn ,
2706+ title = "long worker" ,
2707+ assignee = "ops" ,
2708+ max_runtime_seconds = 3600 ,
2709+ )
2710+ task = kb .get_task (conn , tid )
2711+ workspace = kb .resolve_workspace (task )
2712+ kb ._default_spawn (task , str (workspace ))
2713+ finally :
2714+ conn .close ()
2715+
2716+ assert captured ["env" ]["TERMINAL_TIMEOUT" ] == "3570"
2717+ assert captured ["env" ]["TERMINAL_MAX_FOREGROUND_TIMEOUT" ] == "3570"
2718+ assert os .environ ["TERMINAL_TIMEOUT" ] == "180"
2719+
2720+
2721+ def test_default_spawn_preserves_longer_terminal_timeout (kanban_home , monkeypatch ):
2722+ """Kanban should never lower an explicitly larger terminal timeout."""
2723+ captured = {}
2724+
2725+ class FakeProc :
2726+ pid = 124
2727+
2728+ def fake_popen (cmd , ** kwargs ):
2729+ captured ["env" ] = kwargs .get ("env" , {})
2730+ return FakeProc ()
2731+
2732+ monkeypatch .setattr ("subprocess.Popen" , fake_popen )
2733+ monkeypatch .setenv ("TERMINAL_TIMEOUT" , "7200" )
2734+ monkeypatch .setenv ("TERMINAL_MAX_FOREGROUND_TIMEOUT" , "7200" )
2735+
2736+ conn = kb .connect ()
2737+ try :
2738+ tid = kb .create_task (
2739+ conn ,
2740+ title = "already tuned" ,
2741+ assignee = "ops" ,
2742+ max_runtime_seconds = 3600 ,
2743+ )
2744+ task = kb .get_task (conn , tid )
2745+ workspace = kb .resolve_workspace (task )
2746+ kb ._default_spawn (task , str (workspace ))
2747+ finally :
2748+ conn .close ()
2749+
2750+ assert captured ["env" ]["TERMINAL_TIMEOUT" ] == "7200"
2751+ assert captured ["env" ]["TERMINAL_MAX_FOREGROUND_TIMEOUT" ] == "7200"
2752+
2753+
2754+ def test_default_spawn_leaves_terminal_timeout_without_runtime_cap (kanban_home , monkeypatch ):
2755+ """Uncapped tasks keep the existing terminal timeout behavior."""
2756+ captured = {}
2757+
2758+ class FakeProc :
2759+ pid = 125
2760+
2761+ def fake_popen (cmd , ** kwargs ):
2762+ captured ["env" ] = kwargs .get ("env" , {})
2763+ return FakeProc ()
2764+
2765+ monkeypatch .setattr ("subprocess.Popen" , fake_popen )
2766+ monkeypatch .setenv ("TERMINAL_TIMEOUT" , "180" )
2767+ monkeypatch .delenv ("TERMINAL_MAX_FOREGROUND_TIMEOUT" , raising = False )
2768+
2769+ conn = kb .connect ()
2770+ try :
2771+ tid = kb .create_task (conn , title = "uncapped" , assignee = "ops" )
2772+ task = kb .get_task (conn , tid )
2773+ workspace = kb .resolve_workspace (task )
2774+ kb ._default_spawn (task , str (workspace ))
2775+ finally :
2776+ conn .close ()
2777+
2778+ assert captured ["env" ]["TERMINAL_TIMEOUT" ] == "180"
2779+ assert "TERMINAL_MAX_FOREGROUND_TIMEOUT" not in captured ["env" ]
2780+
2781+
2782+ def test_build_worker_context_includes_runtime_timeout_budget (kanban_home , monkeypatch ):
2783+ monkeypatch .setenv ("TERMINAL_TIMEOUT" , "180" )
2784+ conn = kb .connect ()
2785+ try :
2786+ tid = kb .create_task (
2787+ conn ,
2788+ title = "long context" ,
2789+ assignee = "ops" ,
2790+ max_runtime_seconds = 3600 ,
2791+ )
2792+ ctx = kb .build_worker_context (conn , tid )
2793+ finally :
2794+ conn .close ()
2795+
2796+ assert "Max runtime: 3600s" in ctx
2797+ assert "Terminal timeout: 3570s" in ctx
2798+
2799+
26822800
26832801# ---------------------------------------------------------------------------
26842802# Per-task force-loaded skills
0 commit comments