From e80186f56064eaf93a2f32c48d31d84816894774 Mon Sep 17 00:00:00 2001 From: Godopu Date: Fri, 25 Sep 2026 16:23:06 +0900 Subject: [PATCH] fix(issue-34): merge review - the remote runner's deadline wording counts as an unconfirmed close remote.py closes a job whose end it could not see with `error --detail "deadline_timeout (remote runner)"`; the sub-job table matched the reason exactly and filed it as a plain failure, losing the unconfirmed count and the late-write warning (merge review, codex R2). The kind is the first word of the reason now. Test T14 replays the remote runner's close through the real registry and producer; it fails without the fix. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff --- .agents/skills/lib_py/loop/runner.py | 5 ++++- tests/test_issue34_job_tree.py | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.agents/skills/lib_py/loop/runner.py b/.agents/skills/lib_py/loop/runner.py index 539daff..97cd4e6 100644 --- a/.agents/skills/lib_py/loop/runner.py +++ b/.agents/skills/lib_py/loop/runner.py @@ -691,7 +691,10 @@ class LoopRunner: events=("completed", "error", "cancelled")) or "" if status == "completed": return "completed", reason or "-" - if reason in ORCHESTRATOR_CLOSED or (status == "error" and not reason): + # merge review R2 (codex): the remote runner phrases its deadline as + # "deadline_timeout (remote runner)", so the kind is the first word + closed_kind = reason.split(" ", 1)[0] if reason else "" + if closed_kind in ORCHESTRATOR_CLOSED or (status == "error" and not reason): # no recorded reason is not the agent's own report either return "CLOSED-UNCONFIRMED", reason or "-" if status == "cancelled" and (reason in ("session_busy", "target_not_found") or reason.startswith("rolled back")): diff --git a/tests/test_issue34_job_tree.py b/tests/test_issue34_job_tree.py index fded981..866b6ef 100644 --- a/tests/test_issue34_job_tree.py +++ b/tests/test_issue34_job_tree.py @@ -251,6 +251,26 @@ def test_t13_a_sub_job_cancelled_before_injection_is_not_dispatched_in_the_table assert f"| NOT-DISPATCHED | {reason} |" in block, block +def test_t14_the_remote_runners_deadline_wording_is_closed_unconfirmed(tmp_path, monkeypatch): + """Merge review R2 (codex): remote.py closes a job it could not see finish + with `error --detail "deadline_timeout (remote runner)"`; the exact-match + classifier filed it as a plain failure and the unconfirmed count and its + warning were lost.""" + env = _env(tmp_path) + reg = tmp_path / "reg" + p = _jid(_submit(env, reg)) + c = _jid(_submit(env, reg, "--parent-job", p)) + tok = _record(reg, c)["dispatch_token"] + res = subprocess.run([sys.executable, str(REGISTRY), "--registry-dir", str(reg), "close", "--job", c, + "--request", "deadline_timeout", "--expect-dispatch-token", tok], + capture_output=True, text=True, env=env, timeout=120) + assert res.returncode == 0, res.stderr + _publish(env, reg, c, "error", "deadline_timeout (remote runner)") + block = _runner(tmp_path, monkeypatch)._subjobs_block([p]) + assert f"| {c} | " in block and "| CLOSED-UNCONFIRMED | deadline_timeout (remote runner) |" in block, block + assert "미확인 종결 1" in block, block + + def test_t7_a_job_without_children_is_waited_for_as_before(tmp_path, monkeypatch): env = _env(tmp_path) reg = tmp_path / "reg"