"""Execution-loop edge coverage for preparation, callbacks, and recovery.""" from __future__ import annotations import sqlite3 import sys from contextlib import nullcontext from pathlib import Path from types import SimpleNamespace from testing.tests.test_hermes_cli_support import _completed_result, lanes class _Connection: def close(self): return None def test_missing_claim_is_a_noop(tmp_path: Path, monkeypatch): db = SimpleNamespace( scoped_current_board=lambda _board: nullcontext(), connect=lambda board: _Connection(), get_task=lambda *_args: None, ) monkeypatch.setitem(sys.modules, "hermes_cli", SimpleNamespace(kanban_db=db)) lanes.execute_claim("cassandra", "missing") def test_transient_callback_storage_errors_do_not_kill_provider( tmp_path: Path, monkeypatch, ): task = SimpleNamespace( id="t_callbacks", status="running", current_run_id=20, assignee="cli-auto", max_runtime_seconds=60, ) blocks = [] db = SimpleNamespace( scoped_current_board=lambda _board: nullcontext(), connect=lambda board: _Connection(), get_task=lambda *_args: task, worker_log_path=lambda *_args, **_kwargs: tmp_path / "worker.log", _resolve_worktree_workspace=lambda *_args, **_kwargs: (tmp_path, "branch"), set_branch_name=lambda *_args: None, set_workspace_path=lambda *_args: None, build_worker_context=lambda *_args: "exercise callbacks", heartbeat_worker=lambda *_args, **_kwargs: (_ for _ in ()).throw( sqlite3.OperationalError("heartbeat volume") ), add_comment=lambda *_args, **_kwargs: (_ for _ in ()).throw( sqlite3.OperationalError("comment volume") ), block_task=lambda *_args, **kwargs: blocks.append(kwargs), ) monkeypatch.setitem(sys.modules, "hermes_cli", SimpleNamespace(kanban_db=db)) monkeypatch.setattr(lanes, "STATE_ROOT", tmp_path / "lanes") monkeypatch.setattr(lanes, "KANBAN_STORAGE_ATTEMPTS", 1) monkeypatch.setattr(lanes, "fresh_unavailable_provider", lambda *_args, **_kwargs: "claude") route = lanes.Route("codex", "gpt", "high", "p", "c", "r", 1, ()) monkeypatch.setattr(lanes, "select_route", lambda *_args, **_kwargs: route) def provider(*args, **_kwargs): assert args[6]("still working") is True return lanes.ProcessResult(1, "failed", None, False) monkeypatch.setattr(lanes, "run_provider", provider) lanes.execute_claim("cassandra", "t_callbacks") assert blocks and blocks[0]["kind"] == "capability" def test_restart_handoff_survives_missing_prior_log(tmp_path: Path, monkeypatch): task = SimpleNamespace( id="t_restart", status="running", current_run_id=21, assignee="cli-auto", max_runtime_seconds=60, ) state_root = tmp_path / "lanes" monkeypatch.setattr(lanes, "STATE_ROOT", state_root) state_file = lanes.state_path("cassandra", "t_restart") lanes.atomic_json(state_file, {"current_route": {"provider": "claude"}}) blocks = [] db = SimpleNamespace( scoped_current_board=lambda _board: nullcontext(), connect=lambda board: _Connection(), get_task=lambda *_args: task, worker_log_path=lambda *_args, **_kwargs: tmp_path / "missing.log", _resolve_worktree_workspace=lambda *_args, **_kwargs: (tmp_path, "branch"), set_branch_name=lambda *_args: None, set_workspace_path=lambda *_args: None, build_worker_context=lambda *_args: "resume", add_comment=lambda *_args, **_kwargs: None, heartbeat_worker=lambda *_args, **_kwargs: True, block_task=lambda *_args, **kwargs: blocks.append(kwargs), ) monkeypatch.setitem(sys.modules, "hermes_cli", SimpleNamespace(kanban_db=db)) route = lanes.Route("codex", "gpt", "high", "p", "c", "r", 1, ()) monkeypatch.setattr(lanes, "select_route", lambda *_args, **_kwargs: route) handoffs = [] monkeypatch.setattr( lanes, "git_handoff", lambda _workspace, output: handoffs.append(output) or "handoff", ) monkeypatch.setattr( lanes, "run_provider", lambda *_args, **_kwargs: lanes.ProcessResult(1, "failed", None, False), ) lanes.execute_claim("cassandra", "t_restart") assert handoffs == ["Previous provider log was unavailable after restart."] assert blocks def test_unreplayable_persistence_error_recovers_exact_run( tmp_path: Path, monkeypatch, ): task = SimpleNamespace( id="t_persist", status="running", current_run_id=22, assignee="cli-auto", max_runtime_seconds=60, ) comments = [] db = SimpleNamespace( scoped_current_board=lambda _board: nullcontext(), connect=lambda board: _Connection(), get_task=lambda *_args: task, worker_log_path=lambda *_args, **_kwargs: tmp_path / "worker.log", _resolve_worktree_workspace=lambda *_args, **_kwargs: (tmp_path, "branch"), set_branch_name=lambda *_args: None, set_workspace_path=lambda *_args: None, build_worker_context=lambda *_args: "finish", add_comment=lambda _conn, _task, _author, body: comments.append(body), heartbeat_worker=lambda *_args, **_kwargs: True, block_task=lambda *_args, **_kwargs: None, ) monkeypatch.setitem(sys.modules, "hermes_cli", SimpleNamespace(kanban_db=db)) monkeypatch.setattr(lanes, "STATE_ROOT", tmp_path / "lanes") monkeypatch.setattr( lanes, "select_route", lambda *_args, **_kwargs: lanes.Route( "codex", "gpt", "high", "p", "c", "r", 1, () ), ) monkeypatch.setattr( lanes, "run_provider", lambda *_args, **_kwargs: lanes.ProcessResult( 0, "", _completed_result("accepted"), False ), ) monkeypatch.setattr( lanes, "_write_terminal_record", lambda *_args, **_kwargs: (_ for _ in ()).throw(OSError("disk")), ) monkeypatch.setattr(lanes, "_has_pending_finalization", lambda *_args: False) recovered = [] monkeypatch.setattr( lanes, "_recover_exact_run", lambda *args: recovered.append(args) or True, ) lanes.execute_claim("cassandra", "t_persist") assert recovered and recovered[0][1].run_id == 22 assert any("terminal replayable=False" in body for body in comments) def test_unexpected_route_exception_blocks_the_exact_run( tmp_path: Path, monkeypatch, ): task = SimpleNamespace( id="t_route", status="running", current_run_id=23, assignee="cli-auto", max_runtime_seconds=60, ) blocks = [] db = SimpleNamespace( scoped_current_board=lambda _board: nullcontext(), connect=lambda board: _Connection(), get_task=lambda *_args: task, worker_log_path=lambda *_args, **_kwargs: tmp_path / "worker.log", _resolve_worktree_workspace=lambda *_args, **_kwargs: (tmp_path, "branch"), set_branch_name=lambda *_args: None, set_workspace_path=lambda *_args: None, build_worker_context=lambda *_args: "route", add_comment=lambda *_args, **_kwargs: None, heartbeat_worker=lambda *_args, **_kwargs: True, block_task=lambda *_args, **kwargs: blocks.append(kwargs), ) monkeypatch.setitem(sys.modules, "hermes_cli", SimpleNamespace(kanban_db=db)) monkeypatch.setattr(lanes, "STATE_ROOT", tmp_path / "lanes") monkeypatch.setattr( lanes, "select_route", lambda *_args, **_kwargs: (_ for _ in ()).throw(RuntimeError("router down")), ) lanes.execute_claim("cassandra", "t_route") assert blocks[0]["expected_run_id"] == 23 assert "router down" in blocks[0]["reason"] def test_capacity_fallback_without_evidence_blocks_with_provider_output( tmp_path: Path, monkeypatch, ): """A fallback turn with no structured result blocks on raw provider output.""" task = SimpleNamespace( id="t_nofallback", status="running", current_run_id=22, assignee="cli-auto", max_runtime_seconds=60, ) blocks = [] db = SimpleNamespace( scoped_current_board=lambda _board: nullcontext(), connect=lambda board: _Connection(), get_task=lambda *_args: task, worker_log_path=lambda *_args, **_kwargs: tmp_path / "worker.log", _resolve_worktree_workspace=lambda *_args, **_kwargs: (tmp_path, "branch"), set_branch_name=lambda *_args: None, set_workspace_path=lambda *_args: None, build_worker_context=lambda *_args: "attempt both providers", add_comment=lambda *_args, **_kwargs: None, heartbeat_worker=lambda *_args, **_kwargs: True, block_task=lambda *_args, **kwargs: blocks.append(kwargs), ) monkeypatch.setitem(sys.modules, "hermes_cli", SimpleNamespace(kanban_db=db)) monkeypatch.setattr(lanes, "STATE_ROOT", tmp_path / "lanes") monkeypatch.setattr(lanes, "fresh_unavailable_provider", lambda *_args, **_kwargs: None) codex = lanes.Route("codex", "gpt", "high", "p", "c", "r", 1, ()) claude = lanes.Route("claude", "fable", "high", "p", "c", "r", 1, ()) monkeypatch.setattr( lanes, "select_route", lambda _prompt, assignee, **_kwargs: claude if assignee == "cli-claude-high" else codex, ) monkeypatch.setattr(lanes, "git_handoff", lambda *_args: "handoff") reports = [ lanes.ProcessResult(1, "usage limit reached", None, True), lanes.ProcessResult(1, "fallback also failed", None, False), ] monkeypatch.setattr(lanes, "run_provider", lambda *_args, **_kwargs: reports.pop(0)) lanes.execute_claim("cassandra", "t_nofallback") assert reports == [] assert blocks and "fallback also failed" in blocks[0]["reason"] assert blocks[0]["kind"] == "capability" def test_worker_blockers_become_the_block_reason_without_goal_mode( tmp_path: Path, monkeypatch, ): """A structured non-completed report blocks with its own stated blockers.""" task = SimpleNamespace( id="t_blocked", status="running", current_run_id=23, assignee="cli-auto", max_runtime_seconds=60, ) blocks = [] db = SimpleNamespace( scoped_current_board=lambda _board: nullcontext(), connect=lambda board: _Connection(), get_task=lambda *_args: task, worker_log_path=lambda *_args, **_kwargs: tmp_path / "worker.log", _resolve_worktree_workspace=lambda *_args, **_kwargs: (tmp_path, "branch"), set_branch_name=lambda *_args: None, set_workspace_path=lambda *_args: None, build_worker_context=lambda *_args: "report the obstacle", add_comment=lambda *_args, **_kwargs: None, heartbeat_worker=lambda *_args, **_kwargs: True, block_task=lambda *_args, **kwargs: blocks.append(kwargs), ) monkeypatch.setitem(sys.modules, "hermes_cli", SimpleNamespace(kanban_db=db)) monkeypatch.setattr(lanes, "STATE_ROOT", tmp_path / "lanes") monkeypatch.setattr(lanes, "fresh_unavailable_provider", lambda *_args, **_kwargs: None) route = lanes.Route("codex", "gpt", "high", "p", "c", "r", 1, ()) monkeypatch.setattr(lanes, "select_route", lambda *_args, **_kwargs: route) structured = { **_completed_result("progress only"), "status": "blocked", "blockers": ["credentials expired", "remote unreachable"], } monkeypatch.setattr( lanes, "run_provider", lambda *_args, **_kwargs: lanes.ProcessResult(0, "out", structured, False), ) lanes.execute_claim("cassandra", "t_blocked") assert blocks and blocks[0]["reason"] == ( "credentials expired; remote unreachable" )