Files
multi-agent-mux/.agents/reports/reviewer-agy-01/report-927ed539.md
T

9.0 KiB

PR #70 (fix/issue-66) Review Report — agy (herdr:reviewer-agy-01)

  • Job ID: 927ed539
  • Issue / PR: Issue #66 / PR #70 (fix/issue-66)
  • Base Commit: 0461a632eb667c7988fd33be92e9a4b36151f22a
  • Head Commit: bb496fde785133ad1dbec288aa7431766efb6eb5 (tree 15a72c9a1933c64018a2f2d9aa9707348905bb4d)
  • Review Worktree: /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261025T023502Z-d6f876/1
  • Host Environment: macOS Darwin arm64 (Darwin 25.3.0), /bin/bash 3.2.57(1)-release, /usr/bin/python3 3.9.6

1. Executive Summary

PR #70 fixes Issue #66, where lib_py.stall_watch passed the unsanitized --target session name to herdr agent get|read|prompt|wait, causing stall_watch to immediately fail with stall_watch:agent_not_found (exit code 2) whenever the session name had been transformed by _sanitize_herdr_agent_name / sanitize_herdr_agent_name at session creation time (e.g. names > 32 characters such as worktree-scoped loop agents, uppercase characters, punctuation, or non-letter leading characters).

All 7 review checklist items, static/Python 3.9 compatibility checks, negative regression checks on base (0461a632eb667c7988fd33be92e9a4b36151f22a), and functional/regression test runs on head (bb496fde785133ad1dbec288aa7431766efb6eb5) pass cleanly with zero blocking or non-blocking defects.


2. Detailed Checklist Verification

1. Does stall_watch.py use sanitize_herdr_agent_name for every herdr call (agent get, agent read, agent prompt, agent wait)?

  • Verified (PASS):
    • In .agents/skills/lib_py/stall_watch.py, HerdrSource.__init__ computes self.name = sanitize_herdr_agent_name(target) while preserving self.target = target:
      • HerdrSource.agent_get (line 183): self._herdr("agent", "get", self.name)
      • HerdrSource.input_empty (line 206): self._herdr("agent", "read", self.name)
      • HerdrSource.prompt (line 211): self._herdr("agent", "prompt", self.name, text)
      • (HerdrSource.agent_pid at line 187 and HerdrSource.blocked_text at line 193 take pane_id (wN:pM) returned by agent get, not the agent name; _herdr at line 168 passes self.session to --session, which is the herdr server session --herdr-session.)
    • In StallWatch.__init__ and StallWatch.run, self.herdr_name = sanitize_herdr_agent_name(args.target) is passed to watcher.wait_once(self.herdr_name, until=until, timeout_ms=tmo, session=self.args.herdr_session).
    • Confirmed that lib_py.agents.sanitize.sanitize_herdr_agent_name and lib.sh::_sanitize_herdr_agent_name produce identical outputs across empty strings, lowercase/uppercase names, leading digits/dashes, >32-char names ({s[:23]}-{sha1(s)[:8]}), and special characters.

2. Are there any other places where stall_watch.py calls herdr with an unsanitized agent name?

3. Does stall_watch.py still keep the original target name for human-facing log lines and notifications?

  • Verified (PASS):
    • All log lines in StallWatch (stall_watch.py:373, 397, 405-406) continue to format self.args.target (the original unsanitized session name):
      • [stall-watch] idle detected for {self.args.target} (job {self.args.job_id}); sending nudge
      • [stall-watch] blocked detected for {self.args.target} (job {self.args.job_id}): {text}
      • [stall-watch] cannot judge input box for {self.args.target} (agent={self.kind}, job {self.args.job_id}); nudge withheld
    • Progress notifications (notify_progress) continue to publish stalled:idle, stalled:blocked ..., stalled:unverified_input ..., and stall_watch:... keyed by --job (self.args.job_id).

4. Does resolve_agent(agent, target) still receive the original target name?

  • Verified (PASS):
    • Both HerdrSource.__init__ (stall_watch.py:163) and StallWatch.__init__ (stall_watch.py:230) pass the original target (args.target) to resolve_agent(...).
    • When --agent is omitted, resolve_agent infers the agent kind from the -<agent> suffix of target (e.g., mam-wt-issue-66-very-long-creator-codex -> codex). Passing the sanitized name (mam-wt-issue-66-very-lo-823a0a24) would have lost the -codex suffix due to 32-character truncation + SHA-1 suffixing. Keeping resolve_agent on the original target is verified by test_the_kind_of_a_long_name_comes_from_its_original_suffix.

5. In tests/conftest.py, is the strict_agent_names opt-in clean and backward-compatible with existing tests?

  • Verified (PASS):
    • In tests/conftest.py, _match_agent(k, target) checks if state.get("strict_agent_names"): return k == target and otherwise falls back to the existing substring match (k == target or target in k or k in target).
    • When strict_agent_names is enabled and an agent is not found, cmd2 == "get" (tests/conftest.py:1058-1061) and cmd2 == "read" (tests/conftest.py:1153-1156) emit {"error": {"code": "agent_not_found", "message": "agent " + name + " not found"}} on stderr with exit code 1, matching wait, prompt, and real herdr.
    • Existing tests that do not set strict_agent_names retain identical behavior.

6. Are the new tests in tests/test_issue66_stall_watch_sanitized_name.py genuine behavioral tests that would fail without the fix in stall_watch.py?

  • Verified (PASS):
    • Executed tests/test_issue66_stall_watch_sanitized_name.py + updated tests/conftest.py against base (0461a632eb667c7988fd33be92e9a4b36151f22a, without the stall_watch.py fix):
      • 5 failed, 2 passed: Every test case where sanitize_herdr_agent_name(target) != target (creator-claude-fix-issue-99-long-branch, Creator-Claude-T, and mam-wt-issue-66-very-long-creator-codex) failed on base (stall_watch:agent_not_found instead of blocked notice, or empty prompt list instead of idle nudge), while the 2 control tests (test_the_names_change_and_the_strict_mock_answers_only_the_sanitized_one and test_an_idle_agent_is_nudged_through_the_sanitized_name[creator-claude-t]) passed.
    • On head (bb496fde785133ad1dbec288aa7431766efb6eb5), all 7 passed.

7. Is the one-off failure in test_issue17_item4_decisions.py::test_c_finalize_ends_with_an_unreaped_wrapper[broker_unavailable-3-0] unrelated to this diff?

  • Verified (PASS):
    • Inspected tests/test_issue17_item4_decisions.py:204-221 and 565-601: that test stubs *lib_py.stall_watch* in its $PY wrapper to exit 0 immediately and tests finalize_unfinished re-observing a live lib_py.delegation.wrapper process across a 3-second broker_unavailable window (touch ready + time.sleep(2) in _terminate_unreaped). It never imports or executes lib_py.stall_watch or strict_agent_names.
    • Both test_c_finalize_ends_with_an_unreaped_wrapper[broker_unavailable-3-0] and [timeout-2-10] passed on head during our verification run.

3. Verification Summary

Check Command / Scope Result
Whitespace / patch hygiene git diff --check 0461a632eb667c7988fd33be92e9a4b36151f22a..bb496fde785133ad1dbec288aa7431766efb6eb5 Clean (exit 0)
macOS system Python 3.9 syntax & import /usr/bin/python3 (3.9.6) ast.parse(..., feature_version=(3, 9)) + import lib_py.stall_watch PY39_AND_SANITIZE_EQUIV_OK (exit 0)
Bash vs Python sanitizer equivalence lib.sh::_sanitize_herdr_agent_name vs sanitize_herdr_agent_name across 8 edge cases Exact match (exit 0)
Negative regression on base (0461a63) pytest tests/test_issue66_stall_watch_sanitized_name.py on 0461a63 + strict mock 5 failed, 2 passed (reproduces stall_watch:agent_not_found)
Behavioral & regression suites on head (bb496fd) pytest tests/test_issue66_stall_watch_sanitized_name.py tests/test_issue30_stall_watch.py tests/test_issue30_stall_watch_stop.py tests/test_issue17_item4_decisions.py -k "..." 91 passed, 1 skipped in 399.91s (exit 0)
Worktree cleanliness git status --short in review worktree Clean (no untracked or modified files)

[VERDICT: PASS]