PR #41(v4.8.0)·PR #43(v4.8.1) 리뷰에서 비차단으로 남긴 후속 사항을 정리한다. 이 워크스페이스의 루프로 creator-claude-01 이 구현하고 네 리뷰어(claude·agy·grok·codex)가 리뷰했다. 버전 bump(4.8.2)는 병합 직전 릴리스 커밋으로 붙는다.
변경
F1tests/test_issue21_acceptance.py::test_a8 가 매달 1~9 일에 실패하던 날짜 의존 버그. 테스트가 마커에 쓰는 ps -o lstart 는 Oct 1 처럼 공백 두 칸인데 loop_lock.sh 는 공백을 한 칸으로 줄여 비교한다. 테스트가 제품과 같은 정규화로 쓴다. 제품 코드는 그대로.
F2 존재하지만 읽을 수 없는 .mam.env 에서 Python 진입점 7 곳이 트레이스백 대신 ERROR: <경로>: cannot read (<예외 종류>) 한 줄과 rc 1. 라이브러리 함수는 종료하지 않는다. 실패는 "이미 읽음"으로 캐시하지 않는다(리뷰 codex R1).
F3.mam.env 첫 줄 UTF-8 BOM 제거, 중복 키는 첫 값이 이김을 문서화, MESSAGING.md 의 MAM_ENV_FILE 설명을 실제 동작에 맞춤.
F4 러너의 MAM_WAIT_GRACE_S·MAM_CHILD_WAIT_S·MAM_JOB_TREE_MAX 와 [ -ge ]/sleep 에 쓰이는 bash 정수 노브 9 개를 같은 정수 규칙으로(잘못된 값은 경고 후 기본값). mam_int 를 lib_int.sh 로 분리. MAM_WAIT_GRACE_S 문서화.
F5 제품 코드의 테스트 훅 MAM_TEST_UNRELIABLE_AGENT 제거, S13 은 테스트 seam 으로.
F6 감시자 수명 문구의 "≤ 60 s" 를 herdr wait 에 한정, "대기 중 래퍼 소멸" 테스트가 두 번째 wait 안에서 소멸을 일으킨다.
PR #41(v4.8.0)·PR #43(v4.8.1) 리뷰에서 비차단으로 남긴 후속 사항을 정리한다. 이 워크스페이스의 루프로 creator-claude-01 이 구현하고 네 리뷰어(claude·agy·grok·codex)가 리뷰했다. 버전 bump(4.8.2)는 병합 직전 릴리스 커밋으로 붙는다.
## 변경
- **F1** `tests/test_issue21_acceptance.py::test_a8` 가 매달 1~9 일에 실패하던 날짜 의존 버그. 테스트가 마커에 쓰는 `ps -o lstart` 는 `Oct 1` 처럼 공백 두 칸인데 `loop_lock.sh` 는 공백을 한 칸으로 줄여 비교한다. 테스트가 제품과 같은 정규화로 쓴다. 제품 코드는 그대로.
- **F2** 존재하지만 읽을 수 없는 `.mam.env` 에서 Python 진입점 7 곳이 트레이스백 대신 `ERROR: <경로>: cannot read (<예외 종류>)` 한 줄과 rc 1. 라이브러리 함수는 종료하지 않는다. 실패는 "이미 읽음"으로 캐시하지 않는다(리뷰 codex R1).
- **F3** `.mam.env` 첫 줄 UTF-8 BOM 제거, 중복 키는 첫 값이 이김을 문서화, `MESSAGING.md` 의 `MAM_ENV_FILE` 설명을 실제 동작에 맞춤.
- **F4** 러너의 `MAM_WAIT_GRACE_S`·`MAM_CHILD_WAIT_S`·`MAM_JOB_TREE_MAX` 와 `[ -ge ]`/`sleep` 에 쓰이는 bash 정수 노브 9 개를 같은 정수 규칙으로(잘못된 값은 경고 후 기본값). `mam_int` 를 `lib_int.sh` 로 분리. `MAM_WAIT_GRACE_S` 문서화.
- **F5** 제품 코드의 테스트 훅 `MAM_TEST_UNRELIABLE_AGENT` 제거, S13 은 테스트 seam 으로.
- **F6** 감시자 수명 문구의 "≤ 60 s" 를 herdr wait 에 한정, "대기 중 래퍼 소멸" 테스트가 두 번째 wait 안에서 소멸을 일으킨다.
## 리뷰
| 회차 | head | claude | agy | grok | codex |
|---|---|---|---|---|---|
| 1 | 95eabc6 | PASS | PASS | PASS | NOT PASS (R1: 설정 읽기 실패를 성공으로 캐시) |
| 2 | 75f364e | PASS | PASS | PASS | PASS |
제품 코드를 바꾸는 항목(F2~F5)의 회귀 테스트는 제품 파일만 main 으로 되돌린 사본에서 47 건 실패한다(claude 독립 확인).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
`ps -o lstart=` pads a one-digit day ("Oct 1"); loop_lock.sh's mam_lstart
squeezes the spaces before comparing, but A8 wrote the raw value, so the
live holder's marker looked stale and the second loop was not refused on
the 1st-9th of every month. The test now normalises like the product (the
product is unchanged) and also runs with a `ps` shim that always prints a
padded lstart, so the padded case is checked on every date.
The other tests that record a live lstart (test_o3_scoped_guard
_get_lstart) already normalise; test_o2's markers carry fixed stale values.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
A .mam.env that exists but cannot be read or decoded stopped the Python
entry points with an EnvFileError traceback, while the wrapper printed one
`ERROR:` line and exited 1 (PR #43 review claude S1). The mains that load the
env file - publish_event, registry, job_subscriber, cli, bench, heartbeat and
remote - now call mqtt_common.load_env_or_report(), which prints
`ERROR: <path>: cannot read (<exception type>)` and lets the main return 1.
No value or file content is printed. ensure_env_loaded() still raises: no
library function exits the process.
Test: every entry point, for a 000 file and an undecodable one, ends with
exactly that line and rc 1 and creates nothing (14 cases; all 14 fail at
0051fd6 with a traceback).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
PR #43 review (grok):
(a) A UTF-8 byte-order mark at the start of the file made the first line an
"invalid key", so its setting was dropped. The one parser (envfile.py,
used by the wrapper and mqtt_common) now reads with utf-8-sig.
(b) A key given twice keeps its first value (a later line does not replace
it). The behaviour is unchanged; .mam.env.example's FORMAT section and
envfile.py's docstring now say so.
(c) MESSAGING.md said a MAM_ENV_FILE naming no file logs an error and makes
connections fail closed. Both loaders skip it silently and refuse the
ambient search; a file that exists but cannot be read is the error
(EnvFileError, one ERROR line and rc 1). The text now says that.
Tests A7: BOM in both loaders, first-value-wins in both loaders plus the
FORMAT text, and a missing explicit file loading neither it nor the ambient
.mam.env without an error plus the MESSAGING text (all three fail at 0051fd6).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
MAM_WAIT_GRACE_S silently fell back to 300 on anything but bare digits
(" 30 " too), MAM_CHILD_WAIT_S went through float() (abc raised ValueError,
0.5 and 1e3 were taken) and MAM_JOB_TREE_MAX through int() (abc and an
empty value raised). All three now go through runner._int_knob, which is
_positive_int's rule (PR #43 review grok): decimal, blanks around allowed,
an invalid value warns once and the default is used. Minimums:
MAM_WAIT_GRACE_S >= 0 (wait exactly the budget), MAM_CHILD_WAIT_S >= 0
(look once; test_issue34 uses 0), MAM_JOB_TREE_MAX >= 1 (a cap of 0 calls
every tree "over"; not waiting is MAM_CHILD_WAIT_S=0).
The loop SKILL.md now names MAM_WAIT_GRACE_S in the budget row and the
sub-job paragraph (PR #41 M6), with the knobs' integer rules.
Tests: RUNNER_INT_KNOBS in test_issue26_ints.py holds the minimums; the
real wait_for_descendants is driven per knob, and a static check ties the
call-site minimums to the table (15 of the new cases fail at 0051fd6; the
14 that pass are valid values whose result did not change).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
A non-numeric MAM_READY_EMPTY_GIVEUP, SKS_EMPTY_GIVEUP, SKS_IDLE_RETRIES,
SKS_IDLE_RETRY_BACKOFF_S, MAM_LOCK_STEAL_RETRIES, MAM_DIFF_MAX_BYTES,
MAM_DIFF_MAX_LINES or RECONCILE_POLL_INTERVAL was a `[` error or a sleep
error at the point of use (PR #43 review claude R3). They now go through
mam_int_knob: decimal, a warning and the default for a bad value. Minimums
are in test_issue26_ints.py INT_KNOBS: 1 for the give-up counts (0 acts as
1), the diff caps (0 truncates every diff) and the poll interval (0 is a
busy loop); 0 for the retry counts (one attempt) and the retry backoff.
SKS_QUIESCENT_INTERVAL stays raw: its default is 0.5 s and sleep's
fraction is its meaning; NOT_ARITHMETIC says so.
loop_lock.sh and diff_collect.sh also run without lib.sh (the Python runner
and the tests source them alone), so mam_int / mam_int_knob move verbatim
to lib_int.sh, which lib.sh sources and those two source when the function
is missing. remove.sh's no-manifest fallback list names the new file
(install copies every file under .agents/skills). test_issue21_loop_b l15,
which builds a control root holding only diff_collect.sh, copies it too.
Tests: the table guard now reads every shell file; scripts sourced alone
read their knobs as decimal; _pane_quiescent with SKS_EMPTY_GIVEUP=abc
gives up with a warning and no `[` error (14 of the new cases fail at
0051fd6).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
stall_watch read MAM_TEST_UNRELIABLE_AGENT to pretend an adapter's
agent_status was unreliable (PR #41 M7). The branch is gone. S13 now runs
the real stall_watch.main() in a process where the adapter itself reports
agent_status_reliable = False (_UNRELIABLE_MAIN), and checks the same
things as before: no idle notice or nudge, the blocked notice, and the
"idle judgement off" log line. A static check keeps MAM_TEST_ out of the
watcher (fails at 0051fd6). Without the seam S13 fails on this tree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
delegate-job SKILL.md said a watcher outlives a killed wrapper by "one
wait, <= 60 s". That bound is the herdr wait's; a wrapper that goes while a
notice is being published is noticed when that publish ends. The text now
says both (PR #41 codex round 3).
"a wrapper gone during the wait" switched alive() by call count, so the
watcher saw the wrapper gone at the next loop head and never entered the
second wait (ticks == [1] with that band). The wrapper now vanishes inside
the second herdr wait (_during_notice's in_wait hook), and the test checks
that the second wait happened and that alive() was true until then. The
same test fails when the post-wait _wrapper_gone() check is removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
5edf913 gave job_subscriber.main a bare `return 1`; its exit codes are
returned by name (test_issue5 test_job_subscriber_cli_contract_snapshot).
The value stays 1 - what the EnvFileError traceback exited with before -
and is RC_ERROR. The wrapper reads the same file first and exits 1 itself,
so it never starts a subscriber that would report this.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
ensure_env_loaded() set _env_loaded before _load_dotenv() ran. With F2,
load_env_or_report() turns the EnvFileError into a return value, so a second
main() in the same process skipped the unreadable file and ran unconfigured
(codex R1: registry.main twice -> rc 1 then rc 0 with a job registered; a
repaired file was never read). The flag is now set only after a successful
load: a failure is retried on the next call, a success still loads once.
envfile parses the whole file before applying, so a failed load sets nothing.
MESSAGING.md: the one-line ERROR is the Python entry points'; the wrapper
adds its own "ERROR: cannot load <path>" line (codex, minor).
Test: in one process, registry.main twice on an undecodable file is rc 1
both times, prints the ERROR line twice and registers nothing; the repaired
file is applied on the next call; a later change is not re-read. At 95eabc6
it fails with rc [1, 0], one job registered and the value unset.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
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
Godopu
merged commit 45d1621c93 into main2026-10-02 15:27:28 +00:00
리뷰: 1회차(@95eabc6) claude·agy·grok PASS, codex NOT PASS — F2 에서 설정 파일 읽기 실패를 "이미 읽음"으로 캐시해, 같은 프로세스에서 다시 호출하면 깨진 파일을 건너뛰고 rc 0 으로 잡을 등록했다. creator 가 75f364e 에서 읽기에 성공했을 때만 캐시하도록 고쳤다. 2회차 4/4 PASS.
전체 스위트 (75f364e, MQTT 환경변수 없이): 1380 passed / 0 failed / 3 skipped(61 분). 매달 1~9 일에 실패하던 test_a8 이 10월 3일에 통과해 F1 이 확인됐다. 첫 실행은 맥 이동으로 93% 에서 중단돼 다시 돌렸다.
## 병합 요약 (v4.8.2)
병합 커밋 45d1621, 릴리스 3563a6f.
- **리뷰**: 1회차(@95eabc6) claude·agy·grok PASS, codex NOT PASS — F2 에서 설정 파일 읽기 실패를 "이미 읽음"으로 캐시해, 같은 프로세스에서 다시 호출하면 깨진 파일을 건너뛰고 rc 0 으로 잡을 등록했다. creator 가 75f364e 에서 읽기에 성공했을 때만 캐시하도록 고쳤다. 2회차 **4/4 PASS**.
- **전체 스위트** (75f364e, MQTT 환경변수 없이): **1380 passed / 0 failed / 3 skipped**(61 분). 매달 1~9 일에 실패하던 `test_a8` 이 10월 3일에 통과해 F1 이 확인됐다. 첫 실행은 맥 이동으로 93% 에서 중단돼 다시 돌렸다.
- **보관**: 두 회차 리뷰 리포트(2회차는 identity 사이드카 포함)와 creator 리포트를 `.agents/reports/<session>/` 에.
### 남은 후속 (비차단)
- `tests/test_o2_race_free_lock.py` 의 호출되지 않는 `get_lstart` 헬퍼는 공백을 정규화하지 않는다 — 지우거나 같은 정규화로(claude T1).
- `reconcile.sh` 의 내장 구독자는 진입점 목록 밖이라 설정 파일 읽기 실패 시 여전히 트레이스백(claude T2).
- bash 정수 검증의 Python 이전은 #17 항목 4 목록에 정리했다.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
PR #41(v4.8.0)·PR #43(v4.8.1) 리뷰에서 비차단으로 남긴 후속 사항을 정리한다. 이 워크스페이스의 루프로 creator-claude-01 이 구현하고 네 리뷰어(claude·agy·grok·codex)가 리뷰했다. 버전 bump(4.8.2)는 병합 직전 릴리스 커밋으로 붙는다.
변경
tests/test_issue21_acceptance.py::test_a8가 매달 1~9 일에 실패하던 날짜 의존 버그. 테스트가 마커에 쓰는ps -o lstart는Oct 1처럼 공백 두 칸인데loop_lock.sh는 공백을 한 칸으로 줄여 비교한다. 테스트가 제품과 같은 정규화로 쓴다. 제품 코드는 그대로..mam.env에서 Python 진입점 7 곳이 트레이스백 대신ERROR: <경로>: cannot read (<예외 종류>)한 줄과 rc 1. 라이브러리 함수는 종료하지 않는다. 실패는 "이미 읽음"으로 캐시하지 않는다(리뷰 codex R1)..mam.env첫 줄 UTF-8 BOM 제거, 중복 키는 첫 값이 이김을 문서화,MESSAGING.md의MAM_ENV_FILE설명을 실제 동작에 맞춤.MAM_WAIT_GRACE_S·MAM_CHILD_WAIT_S·MAM_JOB_TREE_MAX와[ -ge ]/sleep에 쓰이는 bash 정수 노브 9 개를 같은 정수 규칙으로(잘못된 값은 경고 후 기본값).mam_int를lib_int.sh로 분리.MAM_WAIT_GRACE_S문서화.MAM_TEST_UNRELIABLE_AGENT제거, S13 은 테스트 seam 으로.리뷰
95eabc675f364e제품 코드를 바꾸는 항목(F2~F5)의 회귀 테스트는 제품 파일만 main 으로 되돌린 사본에서 47 건 실패한다(claude 독립 확인).
🤖 Generated with Claude Code
`ps -o lstart=` pads a one-digit day ("Oct 1"); loop_lock.sh's mam_lstart squeezes the spaces before comparing, but A8 wrote the raw value, so the live holder's marker looked stale and the second loop was not refused on the 1st-9th of every month. The test now normalises like the product (the product is unchanged) and also runs with a `ps` shim that always prints a padded lstart, so the padded case is checked on every date. The other tests that record a live lstart (test_o3_scoped_guard _get_lstart) already normalise; test_o2's markers carry fixed stale values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDgheMAM_WAIT_GRACE_S silently fell back to 300 on anything but bare digits (" 30 " too), MAM_CHILD_WAIT_S went through float() (abc raised ValueError, 0.5 and 1e3 were taken) and MAM_JOB_TREE_MAX through int() (abc and an empty value raised). All three now go through runner._int_knob, which is _positive_int's rule (PR #43 review grok): decimal, blanks around allowed, an invalid value warns once and the default is used. Minimums: MAM_WAIT_GRACE_S >= 0 (wait exactly the budget), MAM_CHILD_WAIT_S >= 0 (look once; test_issue34 uses 0), MAM_JOB_TREE_MAX >= 1 (a cap of 0 calls every tree "over"; not waiting is MAM_CHILD_WAIT_S=0). The loop SKILL.md now names MAM_WAIT_GRACE_S in the budget row and the sub-job paragraph (PR #41 M6), with the knobs' integer rules. Tests: RUNNER_INT_KNOBS in test_issue26_ints.py holds the minimums; the real wait_for_descendants is driven per knob, and a static check ties the call-site minimums to the table (15 of the new cases fail at 0051fd6; the 14 that pass are valid values whose result did not change). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe병합 요약 (v4.8.2)
병합 커밋
45d1621, 릴리스3563a6f.75f364e에서 읽기에 성공했을 때만 캐시하도록 고쳤다. 2회차 4/4 PASS.75f364e, MQTT 환경변수 없이): 1380 passed / 0 failed / 3 skipped(61 분). 매달 1~9 일에 실패하던test_a8이 10월 3일에 통과해 F1 이 확인됐다. 첫 실행은 맥 이동으로 93% 에서 중단돼 다시 돌렸다..agents/reports/<session>/에.남은 후속 (비차단)
tests/test_o2_race_free_lock.py의 호출되지 않는get_lstart헬퍼는 공백을 정규화하지 않는다 — 지우거나 같은 정규화로(claude T1).reconcile.sh의 내장 구독자는 진입점 목록 밖이라 설정 파일 읽기 실패 시 여전히 트레이스백(claude T2).🤖 Generated with Claude Code