Files
multi-agent-mux/.agents/reports/reviewer-grok-01/report-d50d194d.md
T
GodopuandClaude Opus 5.5 3563a6fc17 release(v4.8.2): bump framework and eight skills to 4.8.2 in lockstep; archive the follow-up loop's reports
PR #41/#43 review follow-ups F1-F6, implemented by creator-claude-01 in a
loop and reviewed by claude, agy, grok and codex: round 1 (@95eabc6)
codex NOT PASS (a failed env load cached as loaded), fixed in 75f364e;
round 2 4/4 PASS. Full suite at 75f364e 1380 passed / 0 failed. Reviewer
reports (round 2 with identity sidecars) and the creator's reports go to
.agents/reports/<session>/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-10-03 00:27:24 +09:00

94 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Review: follow-ups F1–F6 @ 95eabc6
- Role: Reviewer (grok)
- Identity: base `0051fd6c3b088f8b265928ccd94ea31053fdde7e`, head `95eabc693f7346f491592b053487bdcf0acd5f4f`, tree `46399844fa63d62d97f7b52ee35010f2fa1b7998`, 8 commits, clean worktree
- Verified at `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261001T230324Z-b511bf/1`. Main stayed at `0051fd6`. No product file was edited.
- `git diff --check 0051fd6..HEAD` is clean. 27 files, +397/−88. No version bump, and nothing in the #17 item-4 / #39 / #40 / #42 scope.
## F1. Day-of-month marker
`mam_lstart` is unchanged: `ps -o lstart=` piped through `tr -s ' '` and a trim (`loop_lock.sh:10-12`). The only edit in that file is sourcing `lib_int.sh` for F4b.
`test_a8_second_loop_is_refused_before_a_scheduler_exists` now writes the marker with `" ".join(out.split())` (`tests/test_issue21_acceptance.py:268-272`), the same collapse the product applies. It is parametrized `real` / `padded`. The padded case puts a `ps` shim first on `PATH` that answers `lstart=` with `Thu Oct 1 08:02:03 2026` (two spaces) and asserts the marker stored `Thu Oct 1 08:02:03 2026`. The product, through the same shim, collapses to that string, so the holder stays live and the second loop is refused. That no longer depends on the calendar. Today is the 2nd, inside the old 1–9 failure window; this run passed both parameters.
The other live-marker writer, `tests/test_o3_scoped_guard.py:57-59`, already uses `" ".join(out.stdout.split())`. `tests/test_o2_race_free_lock.py:40-44` still has a `.strip()` helper, and nothing calls it. Hard-coded `Jan 1` markers in those files name a dead pid or a deliberately stale date, so the padding is not what the comparison decides.
F1's product behavior was already the collapse. The new test fails if a marker keeps the raw two-space `lstart` while `mam_lstart` collapses it. It passes on the previous `loop_lock.sh` for the same reason.
## F2. One-line EnvFileError
`load_env_or_report` (`mqtt_common.py:129-141`) catches `EnvFileError`, prints `ERROR: {path}: cannot read ({type})`, and returns False. It does not call `sys.exit`. `ensure_env_loaded` still raises, so library callers keep the exception.
Each listed main returns 1 (job_subscriber returns `RC_ERROR`, which is 1 at `job_subscriber.py:54`):
| Entry | Call |
|---|---|
| `publish_event.main` | `publish_event.py:337-338` |
| `registry.main` | `registry.py:536-537` |
| `job_subscriber.main` | `job_subscriber.py:590-593` |
| `cli.main` | `cli.py:60-61` |
| `bench.main` | `bench.py:166-167` |
| `heartbeat.main` | `heartbeat.py:496-497` |
| `remote.main` | `remote.py:463-464` |
`test_a1_python_entry_points_report_an_unreadable_file_in_one_line` (`tests/test_issue26_envfile.py:185-221`) runs each `main()` on a mode-`000` file and on broken UTF-8. It requires rc 1, stderr exactly that one line, the value `one` absent, and no registry directory created. On the previous mains the same call raised, so stderr was a traceback and this assertion failed.
## F3. BOM, repeated keys, missing file
`parse` opens with `encoding="utf-8-sig"` (`envfile.py:60-61`), so one leading BOM is not part of the first key. Both loaders share that parser. `test_a7_a_leading_bom_is_not_part_of_the_first_key` (`:414-425`) requires `FIRST` and `SECOND` loaded and no skip warning. The previous `utf-8` open failed that test.
Repeated keys stay first-wins in `apply` (unchanged). `.mam.env.example` states it (`:24`) and states the BOM (`:23`). `test_a7_a_repeated_key_keeps_its_first_value_as_documented` (`:428-436`) checks both loaders and that sentence. The sentence is what fails on the previous example.
`MESSAGING.md:369` now says a missing explicit `MAM_ENV_FILE` is skipped, ambient search is refused, and a file that exists but cannot be read is `EnvFileError` with the one-line `ERROR`. That matches `mqtt_common.py:51-54` (not a regular file: return) and `envfile.py:63-64` (read or decode failure: raise). `test_a7_a_missing_explicit_file_loads_nothing_and_says_nothing` (`:439-451`) checks both loaders and the new sentence. The previous paragraph said an error is logged, so the doc assertion failed there.
## F4. Integer knobs
`mam_int` / `mam_int_knob` moved unchanged into `lib_int.sh`. `lib.sh` sources it. `loop_lock.sh` and `diff_collect.sh`, which are sourced without `lib.sh`, source it themselves when the function is absent. `reconcile.sh` already sources `lib.sh` before `POLL_INTERVAL` (`reconcile.sh:22`, `:41`). `deploy/remove.sh` lists `lib_int.sh` only in the "is anything installed" fallback, next to `lib.sh`. Install copies the tree; `check_assets_present` is a sample, not the copy list.
(a) `runner._int_knob` (`runner.py:629-644`) uses `_positive_int`. Unset or empty keeps the default with no warning. An invalid value warns once and keeps the default, including `MAM_CHILD_WAIT_S=abc`, which used to raise `ValueError`.
| Knob | Minimum | Default | Call |
|---|---|---|---|
| `MAM_WAIT_GRACE_S` | 0 (wait exactly the budget) | 300 | `runner.py:627-628` |
| `MAM_CHILD_WAIT_S` | 0 (one look; `max_wait` 0 ends on that pass, `runner.py:789-792`) | budget + grace | `runner.py:762-765` |
| `MAM_JOB_TREE_MAX` | 1 (0 would treat every tree as over) | 50 | `runner.py:766-768` |
`RUNNER_INT_KNOBS` (`tests/test_issue26_ints.py:71-75`) is checked against the call sites (`:286-291`). `0.5` for the child wait is now invalid on purpose: item (a) puts that knob on the integer rule. The old `float()` path fails `test_b1_runner_child_wait_is_decimal_or_the_default`.
(b) These now go through `mam_int_knob`, minimums in `INT_KNOBS` (`:46-57`): `MAM_READY_EMPTY_GIVEUP` 5/min 1, `SKS_EMPTY_GIVEUP` 3/min 1, `SKS_IDLE_RETRIES` 2/min 0, `SKS_IDLE_RETRY_BACKOFF_S` 10/min 0, `MAM_LOCK_STEAL_RETRIES` 3/min 0, `MAM_DIFF_MAX_BYTES` 200000/min 1, `MAM_DIFF_MAX_LINES` 4000/min 1, `RECONCILE_POLL_INTERVAL` 15/min 1. `test_issue6` sets the backoff to `0`, which stays legal. `SKS_EMPTY_GIVEUP=abc` used to produce `integer expression expected`; `test_b5_pane_quiescent_gives_up_on_empty_captures_with_a_bad_knob` (`:479-484`) requires the warning and rc 2 without that error. `loop_lock.sh` and `diff_collect.sh` are sourced alone with `abc`, `1.5`, and `0` (`:461-476`).
`SKS_QUIESCENT_INTERVAL` stays raw (`lib.sh` still passes `${SKS_QUIESCENT_INTERVAL:-0.5}` into `sleep`). The default is the fraction `0.5`, and `mam_int` would reject it. `NOT_ARITHMETIC` records that reason (`test_issue26_ints.py:100-101`).
(c) The loop skill states `MAM_WAIT_GRACE_S` (default 300, decimal integer ≥ 0, bad value warns and uses 300) on the job-budget row, and states `MAM_CHILD_WAIT_S` (0 means one look) and `MAM_JOB_TREE_MAX` (default 50, minimum 1) in the sub-job paragraph.
## F5. No test switch in the watcher
`MAM_TEST_UNRELIABLE_AGENT` is gone from `stall_watch.py`. `main` still sets `reliable` from `adapter.agent_status_reliable`. S13 starts a one-shot process that sets that attribute to a property returning False on the adapter class, then calls `sw.main` (`tests/test_issue30_stall_watch.py:196-201`, `:454-471`). The assertions are the same: no idle notice while status is idle, then `stalled:blocked` with the detection text, and the log line `idle judgement off for claude`. `test_s13_the_unreliable_case_needs_no_switch_in_the_product` (`:475-478`) requires `MAM_TEST_` absent from the product file, which fails on the previous source.
## F6. The 60 s bound is the herdr wait
`multi-agent-mux-delegate-job/SKILL.md` now limits `<= 60 s` to one herdr wait, and says a publish already in flight runs to its end. The watcher still returns before a notice when the wrapper is gone after a wait (`stall_watch.py:327-328`), and `_may_prompt` checks again after the notice (`:294-299`, `:406-409`).
`test_a_wrapper_gone_during_the_wait_gets_no_notice_and_no_nudge` (`tests/test_issue30_stall_watch_stop.py:418-436`) no longer flips alive by call count. `in_wait` sets the wrapper gone on tick 2, inside `wait_once`, and the test requires the wait ticks to be `[1, 2]`, every earlier alive check true, and the last check `(2, False)`, with no notice and no nudge. That is the second wait, not the loop head before it. The after-wait check was already in the base; this tightens the test and the sentence around it.
## Out of scope
The diff is the eight `fix(followups)` / `test(followups)` commits for F1–F6. `lib_int.sh` is the integer function those scripts share, not a new judgment. No stall close, budget extension, or session-id pin moved into bash.
## Checks
From the review snapshot, MQTT variables unset, no `.mam.env` exported, interpreter and `AGENT_PYTHON_BIN` the control-plane venv, `--basetemp` outside the review tree:
`tests/test_issue26_envfile.py`, `tests/test_issue26_ints.py`, `tests/test_issue21_acceptance.py`, `tests/test_issue21_loop_b.py`, `tests/test_issue30_stall_watch.py`, `tests/test_issue30_stall_watch_stop.py`, `tests/test_tier1_unit.py`, `tests/test_version_consistency.py`
First run: 301 passed, 1 skipped, 2 failed in 450.42s. The skip is `test_issue30_stall_watch.py`'s `/proc` marker (`not linux`). The two failures were `test_a4_main_checkout_is_unchanged_by_the_whole_cycle` and `test_l11_diff_only_remote_reviewer_gets_shas_and_the_snapshot_diff`: both reject the substring `mam-review`, and the basetemp path was `/tmp/mam-review-d50d194d.*`, so the sandbox's own path contained it. Rerun of those two with basetemp `/tmp/d50follow.*`: 2 passed in 2.28s.
Pre-fix failure of the new assertions is from the diff, not a second pytest on `0051fd6`: F2's exact one-line stderr, F3's BOM load and the two doc sentences, F4's `MAM_CHILD_WAIT_S=abc` and `SKS_EMPTY_GIVEUP=abc`, and F5's `MAM_TEST_` absence. F1 and F6 lock behavior the previous product already had.
## Notes
- `reconcile.sh:173` still calls `ensure_env_loaded()` inside the embedded subscriber. An unreadable `.mam.env` there is still a traceback. F2's list is the seven `main`s, and those return the one line. The library function still does not exit.
- `tests/test_o2_race_free_lock.py:40-44` `get_lstart` still uses `.strip()` only. Nothing calls it.
[VERDICT: PASS]