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
51 lines
7.8 KiB
Markdown
51 lines
7.8 KiB
Markdown
# 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]
|