# 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`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261025T023502Z-d6f876/1/.agents/skills/lib_py/stall_watch.py#L158-L212), `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__`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261025T023502Z-d6f876/1/.agents/skills/lib_py/stall_watch.py#L225-L234) and [`StallWatch.run`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261025T023502Z-d6f876/1/.agents/skills/lib_py/stall_watch.py#L331), `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? - **Verified (`PASS`)**: - Inspected all occurrences of `_herdr`, `herdr`, `wait_once`, `self.target`, and `self.args.target` across [`.agents/skills/lib_py/stall_watch.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261025T023502Z-d6f876/1/.agents/skills/lib_py/stall_watch.py) and [`.agents/skills/lib_py/loop/watcher.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261025T023502Z-d6f876/1/.agents/skills/lib_py/loop/watcher.py). No `herdr` invocation uses 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 `-` 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`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261025T023502Z-d6f876/1/tests/conftest.py#L473-L476), `_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]