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
10 KiB
Review: follow-ups F1–F6 @ 95eabc6
- Role: Reviewer (grok)
- Identity: base
0051fd6c3b088f8b265928ccd94ea31053fdde7e, head95eabc693f7346f491592b053487bdcf0acd5f4f, tree46399844fa63d62d97f7b52ee35010f2fa1b7998, 8 commits, clean worktree - Verified at
/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261001T230324Z-b511bf/1. Main stayed at0051fd6. No product file was edited. git diff --check 0051fd6..HEADis 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:173still callsensure_env_loaded()inside the embedded subscriber. An unreadable.mam.envthere is still a traceback. F2's list is the sevenmains, and those return the one line. The library function still does not exit.tests/test_o2_race_free_lock.py:40-44get_lstartstill uses.strip()only. Nothing calls it.
[VERDICT: PASS]