fix(issue-30-33): review 5ec8ef33 - a multi-line codex draft is not an empty box, blocked notices reach the loop while the job runs, and both clocks are labelled on every close path

- R1 (P1): without rules the input region runs from the last prompt line to
  the end of the pane; only a last line matching the adapter's footer pattern
  (codex: `  <model> · ~/<cwd> · …`) is dropped, so a draft on a second line
  keeps the nudge withheld.
- R2: while the synchronous submit runs, a relay thread finds the job it
  registers (same session, created since the submit) and prints its
  stalled:/stall_watch: notices; before, they appeared only after the job.
- R3: the broker-unavailable disk wait that runs into the deadline is the wall
  clock: wall_clock_timeout (caller rc stays 3).
- R4: a state_change_seq change seen right before the judgement restarts the
  idle window (decision 9: S seconds of continuous idle).

Regression tests are the reviewer's reproductions, each with its passing
twin; the wrapper-level #30 tests now use this environment's broker instead
of the #18 harness's public default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-09-25 14:01:53 +00:00
1 parent 47e42744e7
commit bf0f5c1014
8 files changed
+269 -12

No files matched your search

+27
View File
@@ -20,6 +20,7 @@ import sqlite3
import subprocess
import sys
import tempfile
import threading
import time
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Tuple
@@ -534,6 +535,11 @@ class LoopRunner:
env = dict(os.environ)
if "DELEGATE_JOB_PYTHON" not in env:
env["DELEGATE_JOB_PYTHON"] = sys.executable
stop = threading.Event()
relay = threading.Thread(target=self._relay_while_submitting,
args=(self.session_label(agent_session), job_registry._utcnow(), stop),
daemon=True)
relay.start()
try:
return subprocess.run(cmd, capture_output=True, text=True, cwd=str(self.workspace_root), env=env)
except OSError as e:
@@ -543,6 +549,8 @@ class LoopRunner:
f"(prompt {len(p_text.encode('utf-8'))} bytes, wrapper {wrapper_script})")
return subprocess.CompletedProcess(cmd, 126, "", msg)
finally:
stop.set()
relay.join(timeout=5)
try:
os.unlink(prompt_file)
except OSError:
@@ -652,6 +660,25 @@ class LoopRunner:
log_error(f"Job '{job_id}' timed out after {max_wait}s.")
return False
def _relay_while_submitting(self, label: str, since: str, stop: threading.Event) -> None:
"""Issue #30 (review R2): the wrapper runs until its job ends and its
output is captured, so a blocked agent would only reach the loop log
after the job. While the submit runs, find the job it registers (this
session, created since the submit started) and relay its notices."""
jid = None
while not stop.wait(1.0):
if jid is None:
try:
jobs = job_registry.list_jobs(str(self.jobs_dir))
except (OSError, ValueError):
continue
mine = [r for r in jobs if r.get("agent_session") == label
and str(r.get("created_at") or "") >= since]
if not mine:
continue
jid = str(mine[-1].get("job_id"))
self._relay_stall_warnings(jid)
def _relay_stall_warnings(self, job_id: str) -> None:
"""Issue #30: surface the stall watcher's progress events (detail
`stalled:...` / `stall_watch:...`) in the loop log, once per detail.