Files
multi-agent-mux/.agents/reports/reviewer-grok-01/report-5e7e2324.md
T
GodopuandClaude Opus 5.5 7cf728e9f8 release(v4.9.4): the audit log follows a moved registry (Issue #57, PR #58) - archive the review reports and bump the lockstep version
Review loop (--review-only, --all-reviewer, focused on this Mac:
macOS, /usr/bin/python3 3.9.6, bash 3.2): round 1 (@63d8164) agy and
grok PASS, claude and codex NOT PASS (F1 paho via the /usr/bin/python3
fallback, F2 logs default path, R1 disk-fallback redispatch
interleaving, R2 symlinked registry), sent as a PR comment and fixed by
the author in four commits; round 2 (@a7cb90e) 4/4 PASS.

Full suite on macOS / Python 3.9.6 @a7cb90e: 1867 passed, 56 skipped,
1 error (the known #55 false positive: WAL/SHM of an agy conversation
running in another workspace). Real herdr panes and ~/.claude/projects
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-10-15 22:52:04 +09:00

7.8 KiB
Raw Blame History

Review 5e7e2324 — PR #58, Issue #57 follow-up

Reviewed only the snapshot /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261014T152849Z-f1a415/1 (loop-20261014T152849Z-f1a415). Head 63d816468e6d48d01bd96f31d8a25eeadfbaab60, tree e8f162f74b7ce8ee8cc6ba7954b7d28dfa3f89f6, base e32c9574644a9185813a4dc881812694d62b6204 is an ancestor. Eleven commits, 21 files, +659/−95. git diff --check is clean. Snapshot porcelain is empty and the snapshot has no .mam. Control-plane main is still e32c9574644a9185813a4dc881812694d62b6204, porcelain empty. No product file was edited. The source worktree was not used.

Host tools: /usr/bin/python3 3.9.6, the control-plane venv is also 3.9.6, and /bin/bash is 3.2.57. The changed Python modules parse with ast on 3.9.6. bash -n accepts lib.sh, the delegate-job wrapper, worktree_prepare.sh, status.sh, reconcile.sh, and stop_session.sh. Added lines do not use match, tomllib, or X | Y unions.

The previous holes are closed

The rule is mqtt_common.get_logs_dir(registry_dir) (mqtt_common.py:192-208). A non-blank DELEGATE_JOB_LOGS_DIR is returned unchanged. Otherwise the audit root is sibling_logs_dir (mqtt_common.py:186-189): delegate_job_logs next to os.path.abspath of the registry, or of default_registry_dir() when the caller does not pass one. Writers and readers that know a registry pass it: engine._flush, registry.py register/pick/logs, publish_event.record_outcome, remote.publish_dispatch, the legacy status mirror (mqtt_common.py:528), reconcile.sh, and every job_subscriber append including the reconnect line. runner._fingerprint (runner.py:1087) excludes that same root. Path treats the absolute result as absolute, so workspace_root / logs stays the logs directory.

control_plane.from_env (control_plane.py:54-57) records _abs(sibling_logs_dir(mam_jobs_dir)) when DELEGATE_JOB_LOGS_DIR is blank. forwarded_keys (control_plane.py:77-84) drops delegate_job_logs_dir when that stored path equals the same expression, and prefix emits only those keys. An explicit non-sibling root stays in the prefix. With no overrides, from_env still equals defaults, so the usual binding is not forwarded.

worktree_prepare.sh:239-240 appends DELEGATE_JOB_LOGS_DIR only when the caller already set it. stop_session.sh:154-158 still exports the row's delegate_job_logs_dir when the environment variable is unset. A row written by the new from_env stores the sibling, so that export points at the sibling.

Both get_job_status copies in status.sh (:168-173 and :380-385) search the explicit logs dir and the row's logs dir, then sibling_logs_dir of MAM_JOBS_DIR and of the row's mam_jobs_dir. The script was not executed against a real checkout. The sandbox test below runs it.

job_subscriber._check_disk_fallback (job_subscriber.py:96-121) reads status from the audit dict. A versioned registry record adopts that status only when transition_seq is present and int(aud_seq) >= int(rec_seq). A missing or non-numeric audit sequence is treated as stale for a versioned record. An unversioned record, and a missing record, keep the old rule. A registry status that is already terminal is left as it is. Token and attempt checks still run after that.

The wrapper removes the sixteen $WORKDIR/delegate_job_logs defaults. LOGS_DIR_ABS is set to "" before cmd_submit (multi-agent-mux-delegate-job:203). After cd and cd -P of the registry, cmd_submit assigns LOGS_DIR_ABS="$(_logs_dir_abs)" and exits 1 if that command fails (:281), then exports it (:284). Later prints in that command use $LOGS_DIR_ABS. cmd_logs (:1090-1109) parses --registry-dir itself and passes it to registry.py. It does not read LOGS_DIR_ABS. delegate_logs_dir in lib.sh:2375 is the same one-liner. On failure, delegate_started_recorded skips only the audit file and still reads the registry events.log (lib.sh:2386-2389). delegate_wait_started resolves the root once (lib.sh:2446).

This Mac

/tmp is a symlink to /private/tmp. A probe under /tmp (deleted afterward; no herdr, no real .mam) called from_env and forwarded_keys with the jobs directory given as the /tmp path and again with the /var form of a /private/var path:

  • The stored logs path equals _abs(sibling_logs_dir(mam_jobs_dir)), and delegate_job_logs_dir is absent from forwarded_keys.
  • get_logs_dir of the realpathed jobs directory equals that stored path as a string.
  • from_env of a workspace under /tmp, with cwd / and no MAM_JOBS_DIR, equals defaults. get_logs_dir() is the same directory as that binding.
  • A relative MAM_JOBS_DIR is stored absolute. After chdir("/") the derivable root is still not forwarded.
  • An explicit custom logs directory is forwarded.
  • A jobs directory that is itself a symlink to another parent is recorded next to the target, and that binding is not forwarded. sibling_logs_dir of the symlink path is a different directory, because it uses abspath and does not resolve the link. The wrapper physicalizes the registry with cd -P before it resolves logs, and the pane receives the realpathed MAM_JOBS_DIR, so those two writers agree. A direct get_logs_dir of the symlink path follows the path as given.

logs --registry-dir with no value, under set -euo pipefail, exits 1 with an unbound $2. The same shift pattern is how parse_opts reads every flag. The process exits before it writes. A failing command substitution assigned with || is caught on this bash.

Tests

Pytest used the control-plane venv (Python 3.9.6), PYTHONDONTWRITEBYTECODE=1, -p no:cacheprovider, and --basetemp /tmp/mam57-5e7e2324-pytest. Inherited HERDR_*, MAM_*, MQTT_*, SKS_*, WORKSPACE_ROOT, DELEGATE_JOB_LOGS_DIR, PYTHONPATH, HOME_DIR, CLAUDE_PROJECT_DIR, and CODEX_HOME were dropped. AGENT_PYTHON_BIN and DELEGATE_JOB_PYTHON pointed at that venv.

On the snapshot, 23 tests passed in 122.75s: tests/test_issue57_logs_follow_registry.py (resolver, registry CLI, wrapper dry-run and unreachable broker, publish, binding, explicit override, worktree prepare, status json and text, conflict and legacy claim, redispatched-attempt audit, prefix, create, and the pane that moves logs with --registry-dir), plus test_logs_dir_follows_the_same_order, test_t3_0_control_plane_module, test_t3_1_create_persists_and_forwards_a_foreign_binding, test_t3_2_default_binding_is_recorded_not_forwarded, test_t3_3_4_stop_keeps_the_binding_and_resume_reforwards_it, test_a2_pane_env_carries_the_binding_not_the_agent_args, and test_b9_logs_dir_follows_cwd_changes.

The same four new tests copied onto an archive of base e32c957 failed in 0.16s: get_logs_dir() rejected the registry argument, from_env kept the workspace logs directory, the disk fallback did not adopt an audit-only terminal status, and the prefix test did not see the sibling on the row. The archive and both basetemps were removed. Snapshot porcelain stayed empty.

The full suite was left to the orchestrator. tests/test_issue10_13_purge.py was not run.

Documentation left as written

SKILL.md:139-141 and registry.md:152-154 state the sibling rule. SKILL.md:175 (Audit Logs) still names only .mam/delegate_job_logs/<job_id>/, which is the directory the rule produces when the registry is .mam/jobs. docs/design/worktree-creator-model.md:126 lists the status candidates and does not mention the sibling lookup that status.sh now performs. README.md is outside this diff and still names the workspace directory. Those sentences do not change where the code writes.

The pane herdr config file is newer than the mtime recorded at the end of the previous review. This review did not start herdr and did not write that file. ~/.config/herdr/config.toml is unchanged. That mtime is not part of the verdict.

[VERDICT: PASS]