Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
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(tree15a72c9a1933c64018a2f2d9aa9707348905bb4d) - 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__computesself.name = sanitize_herdr_agent_name(target)while preservingself.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_pidat line 187 andHerdrSource.blocked_textat line 193 takepane_id(wN:pM) returned byagent get, not the agent name;_herdrat line 168 passesself.sessionto--session, which is the herdr server session--herdr-session.)
- In
StallWatch.__init__andStallWatch.run,self.herdr_name = sanitize_herdr_agent_name(args.target)is passed towatcher.wait_once(self.herdr_name, until=until, timeout_ms=tmo, session=self.args.herdr_session). - Confirmed that
lib_py.agents.sanitize.sanitize_herdr_agent_nameandlib.sh::_sanitize_herdr_agent_nameproduce identical outputs across empty strings, lowercase/uppercase names, leading digits/dashes, >32-char names ({s[:23]}-{sha1(s)[:8]}), and special characters.
- In
2. Are there any other places where stall_watch.py calls herdr with an unsanitized agent name?
- Verified (
PASS):- Inspected all occurrences of
_herdr,herdr,wait_once,self.target, andself.args.targetacross.agents/skills/lib_py/stall_watch.pyand.agents/skills/lib_py/loop/watcher.py. Noherdrinvocation uses an unsanitized agent name.
- Inspected all occurrences of
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 formatself.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 publishstalled:idle,stalled:blocked ...,stalled:unverified_input ..., andstall_watch:...keyed by--job(self.args.job_id).
- All log lines in
4. Does resolve_agent(agent, target) still receive the original target name?
- Verified (
PASS):- Both
HerdrSource.__init__(stall_watch.py:163) andStallWatch.__init__(stall_watch.py:230) pass the originaltarget(args.target) toresolve_agent(...). - When
--agentis omitted,resolve_agentinfers the agent kind from the-<agent>suffix oftarget(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-codexsuffix due to 32-character truncation + SHA-1 suffixing. Keepingresolve_agenton the originaltargetis verified bytest_the_kind_of_a_long_name_comes_from_its_original_suffix.
- Both
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)checksif state.get("strict_agent_names"): return k == targetand otherwise falls back to the existing substring match (k == target or target in k or k in target). - When
strict_agent_namesis enabled and an agent is not found,cmd2 == "get"(tests/conftest.py:1058-1061) andcmd2 == "read"(tests/conftest.py:1153-1156) emit{"error": {"code": "agent_not_found", "message": "agent " + name + " not found"}}onstderrwith exit code 1, matchingwait,prompt, and realherdr. - Existing tests that do not set
strict_agent_namesretain identical behavior.
- In
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+ updatedtests/conftest.pyagainstbase(0461a632eb667c7988fd33be92e9a4b36151f22a, without thestall_watch.pyfix):- 5 failed, 2 passed: Every test case where
sanitize_herdr_agent_name(target) != target(creator-claude-fix-issue-99-long-branch,Creator-Claude-T, andmam-wt-issue-66-very-long-creator-codex) failed onbase(stall_watch:agent_not_foundinstead 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_oneandtest_an_idle_agent_is_nudged_through_the_sanitized_name[creator-claude-t]) passed.
- 5 failed, 2 passed: Every test case where
- On
head(bb496fde785133ad1dbec288aa7431766efb6eb5), all 7 passed.
- Executed
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-221and565-601: that test stubs*lib_py.stall_watch*in its$PYwrapper toexit 0immediately and testsfinalize_unfinishedre-observing a livelib_py.delegation.wrapperprocess across a 3-secondbroker_unavailablewindow (touch ready+time.sleep(2)in_terminate_unreaped). It never imports or executeslib_py.stall_watchorstrict_agent_names. - Both
test_c_finalize_ends_with_an_unreaped_wrapper[broker_unavailable-3-0]and[timeout-2-10]passed onheadduring our verification run.
- Inspected
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]