리뷰 루프가 diff 를 리뷰 프롬프트에 넣어 bash 에 단일 argv 원소로 넘기던 탓에, 프롬프트가 리눅스의 단일 인자 상한(MAX_ARG_STRLEN, 131,072 바이트)을 넘으면 OSError: [Errno 7] 로 죽고 종료 코드 0 으로 끝나던 문제를 고친다.
변경 요지
프롬프트는 파일로 전달된다. 프롬프트가 지나는 경로 전체에서 argv 로 긴 본문을 넘기던 지점을 없앴다.
파이썬 러너(lib_py/loop/runner.py)는 모든 위임 프롬프트를 제어 루트 .mam 아래 임시 파일에 쓰고 래퍼에 --prompt-file 로 넘긴다. 래퍼가 끝나면 지운다.
delegate-job 래퍼와 등록부(registry.py)가 --prompt-file 을 받는다. 래퍼는 받은 경로를 등록부에 그대로 넘긴다.
옛 셸 러너(MAM_LOOP_PY=0)는 위임 11곳이 모두 거치는 delegate_job_safe 가 프롬프트를 임시 파일로 바꿔 외부 명령에 넘긴다. 쓰기에 실패하면 파일을 지우고 stderr 로 진단한다.
create_session 은 에이전트에게 주입하는 안내문에 과제 본문 대신 잡 디렉터리의 brief 절대경로만 넣는다. delegate-job 래퍼가 원래 하던 방식과 같다.
기존 --prompt 인자는 그대로 받는다.
위임 실패는 성공처럼 끝나지 않는다. 래퍼를 실행할 수 없으면(OSError) 실패한 제출로 다루고, 첫 시도와 재시도 모두에서 원인을 남기고 루프가 종료 코드 1 로 끝난다. 반론 이후의 재심·중재·순응 잡이 등록되지 않은 경우도 판정이 아니라 실패로 끝난다.
수집기 상한의 의미를 다시 정했다.MAM_DIFF_MAX_BYTES·MAM_DIFF_MAX_LINES 는 이제 리뷰어에게 보여줄 증거의 크기만 제한한다. 전달 자체에는 크기 한계가 없다. 잘린 diff 를 증거로 쓸 때의 기존 규칙은 그대로다.
검증
항목
결과
전체 스위트 (59be1c8)
1111 통과 / 12 실패 / 19 건너뜀, 실패 12건은 모두 이 호스트의 기준 실패(tests/test_issue10_13_purge.py)이며 base 에서도 같다
리뷰어 5명
전원 PASS
tests/test_issue36_prompt_transport.py
22 통과
리뷰는 세 라운드로 진행했다. 전체 구현 루프(1debc7d..14d11bf)는 2회차에서 전원 PASS 였다. 이후 남은 argv 지점을 닫은 두 커밋은 각각 델타 리뷰를 받았고, 마지막 라운드(f26952c..59be1c8)에서 전원 PASS 다.
새 테스트는 131,072 바이트를 넘는 실제 프롬프트로 실제 subprocess·exec 경로를 통과시킨다. 수정 전 코드(1debc7d)에 같은 테스트 파일을 두고 돌리면 13건 중 11건이 실패한다.
리뷰에서 나온 지적
codex, 1차. 반론 처리 경로에서 잡 등록이 실패해도 루프가 계속 진행했다. 14d11bf 로 막았다.
오케스트레이터. 1차 PASS 이후에도 옛 셸 러너와 lib.sh 제출 도우미가 프롬프트를 argv 로 넘겼다. f26952c 로 닫았다.
codex, 델타.strace 로 실제 경로를 추적해 두 가지를 재현했다. create_session 이 주입 안내문에 본문 전체를 다시 붙여 130,500 바이트 입력에서 execve 가 E2BIG 로 실패했고, 셸 러너가 쓰기 실패 시 부분 파일을 남겼다. 59be1c8 로 고쳤고, codex 가 제출한 재현 자산을 수정 없이 돌려 통과를 확인했다.
참고
전체 스위트에서 test_issue18_liveness_contract.py 두 건과 test_issue6_fail_closed.py::test_t1_15 가 부하 상태에서 간헐적으로 실패했다. 이번 변경과 무관하다. 수정본에서 따로 돌리면 통과하고, 수정 전 코드에서는 따로 돌려도 실패한다.
Closes #36
리뷰 루프가 diff 를 리뷰 프롬프트에 넣어 `bash` 에 단일 argv 원소로 넘기던 탓에, 프롬프트가 리눅스의 단일 인자 상한(`MAX_ARG_STRLEN`, 131,072 바이트)을 넘으면 `OSError: [Errno 7]` 로 죽고 종료 코드 0 으로 끝나던 문제를 고친다.
## 변경 요지
**프롬프트는 파일로 전달된다.** 프롬프트가 지나는 경로 전체에서 argv 로 긴 본문을 넘기던 지점을 없앴다.
- 파이썬 러너(`lib_py/loop/runner.py`)는 모든 위임 프롬프트를 제어 루트 `.mam` 아래 임시 파일에 쓰고 래퍼에 `--prompt-file` 로 넘긴다. 래퍼가 끝나면 지운다.
- delegate-job 래퍼와 등록부(`registry.py`)가 `--prompt-file` 을 받는다. 래퍼는 받은 경로를 등록부에 그대로 넘긴다.
- 옛 셸 러너(`MAM_LOOP_PY=0`)는 위임 11곳이 모두 거치는 `delegate_job_safe` 가 프롬프트를 임시 파일로 바꿔 외부 명령에 넘긴다. 쓰기에 실패하면 파일을 지우고 stderr 로 진단한다.
- lib.sh 제출 도우미와 `lib_py/delegation/cli.py` 사슬도 파일로 넘긴다.
- create_session 은 에이전트에게 주입하는 안내문에 과제 본문 대신 잡 디렉터리의 brief 절대경로만 넣는다. delegate-job 래퍼가 원래 하던 방식과 같다.
기존 `--prompt` 인자는 그대로 받는다.
**위임 실패는 성공처럼 끝나지 않는다.** 래퍼를 실행할 수 없으면(`OSError`) 실패한 제출로 다루고, 첫 시도와 재시도 모두에서 원인을 남기고 루프가 종료 코드 1 로 끝난다. 반론 이후의 재심·중재·순응 잡이 등록되지 않은 경우도 판정이 아니라 실패로 끝난다.
**수집기 상한의 의미를 다시 정했다.** `MAM_DIFF_MAX_BYTES`·`MAM_DIFF_MAX_LINES` 는 이제 리뷰어에게 보여줄 증거의 크기만 제한한다. 전달 자체에는 크기 한계가 없다. 잘린 diff 를 증거로 쓸 때의 기존 규칙은 그대로다.
## 검증
| 항목 | 결과 |
|---|---|
| 전체 스위트 (`59be1c8`) | 1111 통과 / 12 실패 / 19 건너뜀, 실패 12건은 모두 이 호스트의 기준 실패(`tests/test_issue10_13_purge.py`)이며 base 에서도 같다 |
| 리뷰어 5명 | 전원 PASS |
| `tests/test_issue36_prompt_transport.py` | 22 통과 |
리뷰는 세 라운드로 진행했다. 전체 구현 루프(`1debc7d..14d11bf`)는 2회차에서 전원 PASS 였다. 이후 남은 argv 지점을 닫은 두 커밋은 각각 델타 리뷰를 받았고, 마지막 라운드(`f26952c..59be1c8`)에서 전원 PASS 다.
새 테스트는 131,072 바이트를 넘는 실제 프롬프트로 실제 subprocess·exec 경로를 통과시킨다. 수정 전 코드(`1debc7d`)에 같은 테스트 파일을 두고 돌리면 13건 중 11건이 실패한다.
## 리뷰에서 나온 지적
- **codex, 1차.** 반론 처리 경로에서 잡 등록이 실패해도 루프가 계속 진행했다. `14d11bf` 로 막았다.
- **오케스트레이터.** 1차 PASS 이후에도 옛 셸 러너와 lib.sh 제출 도우미가 프롬프트를 argv 로 넘겼다. `f26952c` 로 닫았다.
- **codex, 델타.** `strace` 로 실제 경로를 추적해 두 가지를 재현했다. create_session 이 주입 안내문에 본문 전체를 다시 붙여 130,500 바이트 입력에서 `execve` 가 `E2BIG` 로 실패했고, 셸 러너가 쓰기 실패 시 부분 파일을 남겼다. `59be1c8` 로 고쳤고, codex 가 제출한 재현 자산을 수정 없이 돌려 통과를 확인했다.
## 참고
전체 스위트에서 `test_issue18_liveness_contract.py` 두 건과 `test_issue6_fail_closed.py::test_t1_15` 가 부하 상태에서 간헐적으로 실패했다. 이번 변경과 무관하다. 수정본에서 따로 돌리면 통과하고, 수정 전 코드에서는 따로 돌려도 실패한다.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
One argv string is capped at MAX_ARG_STRLEN (131,072 bytes on Linux), so a
prompt passed as --prompt could not reach the registry once it carried a
large diff. submit now takes --prompt-file and passes the same path to
`registry.py register --prompt-file`; the loop type's per-iteration update
goes by file too. --prompt keeps working unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
delegate_job writes the prompt to a temp file under the control root's .mam/
and hands the wrapper --prompt-file; the file is removed once the wrapper
returns. An OSError from starting the wrapper becomes a failed submit (rc 126)
that the existing error branch logs, so the session-busy retry no longer
dereferences a missing result. A retry that yields no job id, and a
compliance job that cannot be registered, are now logged instead of silent.
The collector caps are documented as the size of the evidence a reviewer is
shown; delivery has no cap of its own any more. The loop skill notes that a
pipeline hides the loop's exit code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
S1-S1d push a 140,000-byte prompt through the real wrapper and registry by
file, with a negative control that the same prompt as an argument fails with
E2BIG. S2 runs the real runner against a recording wrapper. S3/S3b run a real
`python -m lib_py.loop` whose wrapper fails or cannot start. S4/S4b/S4c cover
the exec-error handler, the busy retry and the compliance log. S5 sends a
collected diff from the former 131,072..200,000-byte blind spot through whole.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of f5cadd9 (planner-reviewer-codex-01, R1): the compliance job's
registration failure was only logged, so a passing next round ended the
real loop process with rc 0. Compliance now returns 1 when it cannot be
registered or does not finish, as the correction step already does.
Re-adjudication and arbitration also return 1 when their job cannot be
registered; a registered job that yields no verdict keeps the designed
fail-closed fallback.
S4c (stubbed delegate_job, a fixture whose later rounds never pass) is
replaced by S3c: a real `python -m lib_py.loop` process whose wrapper runs
an executable that simulates the agents, refusing the compliance,
re-adjudication or arbitration registration while the next round passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two remaining exec points that carried a prompt as one argv string
(capped at 131,072 bytes):
- run_loop.sh (MAM_LOOP_PY=0): delegate_job_safe, which all eleven shell
delegations go through, turns `--prompt <text>` into `--prompt-file` under
the control root's .mam/ and removes the file once the wrapper returns. Its
failure lines go to stderr: callers capture stdout, and under set -e a
failed submit ends the loop at that line with the wrapper's code, so the
cause was being swallowed.
- lib.sh delegate_submit_job and its legacy twin (create_session's onboarding
and --submit-job): the prompt is written under .mam/ and handed on as
--prompt-file; lib_py.delegation.cli submit accepts --prompt-file and
forwards it to the registry. --prompt keeps working everywhere.
S6/S6b push a 140,000-byte prompt through the CLI and both helpers; S7 runs
the real run_loop.sh with a 140,000-byte plan and a 150,000-byte diff; S7b
fixes a nonzero exit when the implementation or compliance job cannot be
registered. All six fail on 14d11bf.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of f26952c (codex, job cc849cbf):
- P2: create_session registered the task by file but then pasted the whole
task into the onboarding text, which send_keys_safe hands to `herdr agent
prompt` as one argv string. A 130,500-byte --submit-job was registered
intact and never reached the agent (execve E2BIG). The task now goes into
<jobs>/<id>/brief.md, as the delegate-job wrapper does, and the injected
text names that path. The publish command and its dispatch token (#23) are
unchanged. The other send_keys_safe callers (the wrapper's short pointer,
stop_session's exit key) carry no task text.
- P3: delegate_job_safe's write-failure branch left the partial prompt file
behind and logged to stdout, which callers capture. It now removes the file
and logs to stderr.
S8 (real create_session.sh, 100 and 130,500 bytes, strace when present) and
S9 (the real function, RLIMIT_FSIZE write failure) carry the reviewer's
reproductions over; all three fail on f26952c.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- runner._run_cmd: the write of the prompt file was outside the cleanup
and error handling that the exec had (merge review: claude H1, codex R1).
A failed write now removes the partial file and returns the same 126
failed submit an unstartable wrapper does, so the loop ends with rc 1
and no traceback. Test S4c.
- test S9 required the literal "File too large" in stderr; bash 3.2 on
macOS prints nothing for EFBIG, so the whole suite failed on this host
(agy, grok, codex R2). The contract asserted everywhere is rc != 0, no
leftover file and the portable diagnostic line; the libc wording is
checked on Linux only.
- the wrapper accepted an empty --prompt-file and registered an empty
prompt (claude H3); it is refused now. Test S1e.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
Issue #36: delegated prompts travel by file, a delegation that fails ends
the loop with rc 1. VERSIONS.md carries the entry; the merge-side reports
(round 1 @59be1c8 and round 2 @708262a with identity sidecars, base
59be1c8 -> head 708262a, evidence review-tree) are archived under
.agents/reports/<session>/.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
Godopu
merged commit 7a4a0c2091 into main2026-09-24 09:10:39 +00:00
비차단 후속: MAM_DIFF_MAX_* 의 "증거 상한, 전달 무제한" 의미를 loop SKILL.md 에도(H2); 래퍼의 $(<file) 이 후행 개행을 잃어 등록부 프롬프트와 브리프 본문이 다를 수 있음(H4); 순응 잡의 실행 실패도 rc 1 로 끝내는 강화는 타당하나 로그 문구가 "Failed to register" 라 등록 실패와 구분되지 않음(grok·codex).
## 병합 완료 — main `7a4a0c2` (v4.7.3, Gitea merge 커밋)
**병합 워크스페이스 재검토** (PR head 를 자체 venv 워크트리에 받아 `--worktree --base --review-only --all-reviewer`):
- 1회차 @59be1c8: claude PASS, agy·grok·codex NOT PASS. 지적은 세 갈래.
- 새 테스트 S9 가 stderr 의 리터럴 `File too large` 를 요구해 macOS bash 3.2 에서 실패(agy·grok·codex R2). 전체 스위트도 이 1건만 실패(1139 통과 / 2 skip).
- Python 러너 `_run_cmd` 의 프롬프트 파일 **쓰기** 실패가 정리·오류 처리 범위 밖이라 부분 파일이 `.mam` 에 남고 예외가 루프까지 올라옴 — PR 이 셸 쪽 두 곳에서 고친 부류가 러너에 빠져 있었다(claude H1·codex R1, 재현).
- 래퍼가 빈 `--prompt-file` 을 받아 빈 프롬프트를 등록(claude H3).
- 정정 `708262a`: 쓰기 실패는 exec 실패와 같은 rc 126 실패 제출(래퍼 미실행, 부분 파일 제거), libc 문구는 Linux 에서만 단언, 빈 파일 거부. 회귀 S4c·S1e 는 정정을 되돌리면 실패.
- 2회차 @708262a: claude·agy·grok·codex **4인 전원 PASS**(identity 4건 base 59be1c8 → head 708262a, evidence review-tree). 관련 스위트 125 통과 / 1 skip.
- codex 는 이 호스트의 `SC_ARG_MAX` 를 넘는 1,114,112 바이트 프롬프트가 argv 로는 `OSError(errno=7)`, 파일로는 공백 경로·다른 `--workdir` 조합에서도 원문 그대로 등록됨을 실측했다.
- 릴리스 `c3e6a7e`: 4.7.2 → 4.7.3 락스텝, 리포트 8건 + identity 4건 아카이브.
**비차단 후속**: `MAM_DIFF_MAX_*` 의 "증거 상한, 전달 무제한" 의미를 loop SKILL.md 에도(H2); 래퍼의 `$(<file)` 이 후행 개행을 잃어 등록부 프롬프트와 브리프 본문이 다를 수 있음(H4); 순응 잡의 실행 실패도 rc 1 로 끝내는 강화는 타당하나 로그 문구가 "Failed to register" 라 등록 실패와 구분되지 않음(grok·codex).
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.
Closes #36
리뷰 루프가 diff 를 리뷰 프롬프트에 넣어
bash에 단일 argv 원소로 넘기던 탓에, 프롬프트가 리눅스의 단일 인자 상한(MAX_ARG_STRLEN, 131,072 바이트)을 넘으면OSError: [Errno 7]로 죽고 종료 코드 0 으로 끝나던 문제를 고친다.변경 요지
프롬프트는 파일로 전달된다. 프롬프트가 지나는 경로 전체에서 argv 로 긴 본문을 넘기던 지점을 없앴다.
lib_py/loop/runner.py)는 모든 위임 프롬프트를 제어 루트.mam아래 임시 파일에 쓰고 래퍼에--prompt-file로 넘긴다. 래퍼가 끝나면 지운다.registry.py)가--prompt-file을 받는다. 래퍼는 받은 경로를 등록부에 그대로 넘긴다.MAM_LOOP_PY=0)는 위임 11곳이 모두 거치는delegate_job_safe가 프롬프트를 임시 파일로 바꿔 외부 명령에 넘긴다. 쓰기에 실패하면 파일을 지우고 stderr 로 진단한다.lib_py/delegation/cli.py사슬도 파일로 넘긴다.기존
--prompt인자는 그대로 받는다.위임 실패는 성공처럼 끝나지 않는다. 래퍼를 실행할 수 없으면(
OSError) 실패한 제출로 다루고, 첫 시도와 재시도 모두에서 원인을 남기고 루프가 종료 코드 1 로 끝난다. 반론 이후의 재심·중재·순응 잡이 등록되지 않은 경우도 판정이 아니라 실패로 끝난다.수집기 상한의 의미를 다시 정했다.
MAM_DIFF_MAX_BYTES·MAM_DIFF_MAX_LINES는 이제 리뷰어에게 보여줄 증거의 크기만 제한한다. 전달 자체에는 크기 한계가 없다. 잘린 diff 를 증거로 쓸 때의 기존 규칙은 그대로다.검증
59be1c8)tests/test_issue10_13_purge.py)이며 base 에서도 같다tests/test_issue36_prompt_transport.py리뷰는 세 라운드로 진행했다. 전체 구현 루프(
1debc7d..14d11bf)는 2회차에서 전원 PASS 였다. 이후 남은 argv 지점을 닫은 두 커밋은 각각 델타 리뷰를 받았고, 마지막 라운드(f26952c..59be1c8)에서 전원 PASS 다.새 테스트는 131,072 바이트를 넘는 실제 프롬프트로 실제 subprocess·exec 경로를 통과시킨다. 수정 전 코드(
1debc7d)에 같은 테스트 파일을 두고 돌리면 13건 중 11건이 실패한다.리뷰에서 나온 지적
14d11bf로 막았다.f26952c로 닫았다.strace로 실제 경로를 추적해 두 가지를 재현했다. create_session 이 주입 안내문에 본문 전체를 다시 붙여 130,500 바이트 입력에서execve가E2BIG로 실패했고, 셸 러너가 쓰기 실패 시 부분 파일을 남겼다.59be1c8로 고쳤고, codex 가 제출한 재현 자산을 수정 없이 돌려 통과를 확인했다.참고
전체 스위트에서
test_issue18_liveness_contract.py두 건과test_issue6_fail_closed.py::test_t1_15가 부하 상태에서 간헐적으로 실패했다. 이번 변경과 무관하다. 수정본에서 따로 돌리면 통과하고, 수정 전 코드에서는 따로 돌려도 실패한다.🤖 Generated with Claude Code
병합 완료 — main
7a4a0c2(v4.7.3, Gitea merge 커밋)병합 워크스페이스 재검토 (PR head 를 자체 venv 워크트리에 받아
--worktree --base --review-only --all-reviewer):File too large를 요구해 macOS bash 3.2 에서 실패(agy·grok·codex R2). 전체 스위트도 이 1건만 실패(1139 통과 / 2 skip)._run_cmd의 프롬프트 파일 쓰기 실패가 정리·오류 처리 범위 밖이라 부분 파일이.mam에 남고 예외가 루프까지 올라옴 — PR 이 셸 쪽 두 곳에서 고친 부류가 러너에 빠져 있었다(claude H1·codex R1, 재현).--prompt-file을 받아 빈 프롬프트를 등록(claude H3).708262a: 쓰기 실패는 exec 실패와 같은 rc 126 실패 제출(래퍼 미실행, 부분 파일 제거), libc 문구는 Linux 에서만 단언, 빈 파일 거부. 회귀 S4c·S1e 는 정정을 되돌리면 실패.59be1c8→ head708262a, evidence review-tree). 관련 스위트 125 통과 / 1 skip.SC_ARG_MAX를 넘는 1,114,112 바이트 프롬프트가 argv 로는OSError(errno=7), 파일로는 공백 경로·다른--workdir조합에서도 원문 그대로 등록됨을 실측했다.c3e6a7e: 4.7.2 → 4.7.3 락스텝, 리포트 8건 + identity 4건 아카이브.비차단 후속:
MAM_DIFF_MAX_*의 "증거 상한, 전달 무제한" 의미를 loop SKILL.md 에도(H2); 래퍼의$(<file)이 후행 개행을 잃어 등록부 프롬프트와 브리프 본문이 다를 수 있음(H4); 순응 잡의 실행 실패도 rc 1 로 끝내는 강화는 타당하나 로그 문구가 "Failed to register" 라 등록 실패와 구분되지 않음(grok·codex).