2026-09-14 Linux 호스트에서 create_session.sh --onboard 로 claude 세션 두 개(planner-reviewer-claude-01, creator-claude-01)를 만들 때 다음이 순서대로 일어났다.
✅ claude TUI detected ready. 뒤 온보딩 job 등록.
등록 3초 만에send_keys_safe: delivery unknown/failed → error 이벤트 instruction injection failed (prompt-lock, rc=4) 발행(job 415e0daa, dc79c903).
⚠️ Error occurred during initialization (exit code 1). Rolling back and killing herdr session ... 출력, 스크립트 rc=1.
그러나 세션은 살아 있었다. 롤백의 kill 이 실패했는데 무시됐다.
.mam/agent-sessions.yaml 행은 status: running 으로 남았고 delegate_job_id 는 error 잡을 가리킨다(status.sh 의 JOB_STATUS 열이 계속 error).
몇 분 뒤 같은 세션에 같은 온보딩 프롬프트를 multi-agent-mux-delegate-job submit 으로 보내자 수 초 안에 started → completed 가 왔다. 즉 에이전트는 정상이었고, 판정만 틀렸다.
원인
전달 판정: create_session.sh:558 의 inject_instructions → send_keys_safe 경로는 #18 에서 래퍼에 들어간 "주입 후 T 초 안에 started 를 기다리는" 확인을 거치지 않는다. agent prompt --wait --until working 이 실패하고 2초 뒤에도 working 이 아니면 곧바로 rc 4 다. 막 뜬 TUI 는 초기화 중이라 여기에 걸린다.
롤백: create_session.sh:264-271 의 trap 은 _herdr kill-session -t "$SESSION_NAME" 2>/dev/null || true 로 실패를 삼킨다. kill 이 실패한 이유는 조사하지 않았다.
상태 불일치: YAML 행(atomic_dump_yaml)과 레지스트리 등록(delegate_submit_job)이 주입보다 먼저 끝나므로, 롤백이 세션을 죽이더라도 YAML·레지스트리는 되돌려지지 않는다.
제안
온보딩 주입도 래퍼와 같은 started 기반 확인(T = ack timeout)으로 판정한다. 가장 간단하게는 온보딩을 래퍼 submit 경로로 보낸다.
롤백은 kill 결과를 확인한다. 실패하면 경고하고, 세션이 살아 있으면 "롤백 실패"를 별도 rc 로 알린다.
롤백 시 YAML 행을 error/terminated 로 표시하거나 제거하고, 실패한 delegate_job_id 를 정리한다.
환경 정보
MAM v4.5.0 (당시 main 5611859), 현재 main 1134d74 에서도 해당 코드 동일
Linux, herdr 격리 세션 multi-agent-mux, claude Code v2.1.270
## 증상
2026-09-14 Linux 호스트에서 `create_session.sh --onboard` 로 claude 세션 두 개(`planner-reviewer-claude-01`, `creator-claude-01`)를 만들 때 다음이 순서대로 일어났다.
1. `✅ claude TUI detected ready.` 뒤 온보딩 job 등록.
2. **등록 3초 만에** `send_keys_safe: delivery unknown/failed` → error 이벤트 `instruction injection failed (prompt-lock, rc=4)` 발행(job `415e0daa`, `dc79c903`).
3. `⚠️ Error occurred during initialization (exit code 1). Rolling back and killing herdr session ...` 출력, 스크립트 rc=1.
4. 그러나 **세션은 살아 있었다.** 롤백의 kill 이 실패했는데 무시됐다.
5. `.mam/agent-sessions.yaml` 행은 `status: running` 으로 남았고 `delegate_job_id` 는 error 잡을 가리킨다(`status.sh` 의 JOB_STATUS 열이 계속 `error`).
몇 분 뒤 같은 세션에 같은 온보딩 프롬프트를 `multi-agent-mux-delegate-job submit` 으로 보내자 수 초 안에 `started` → `completed` 가 왔다. 즉 에이전트는 정상이었고, 판정만 틀렸다.
## 원인
- **전달 판정**: `create_session.sh:558` 의 `inject_instructions` → `send_keys_safe` 경로는 #18 에서 래퍼에 들어간 "주입 후 T 초 안에 `started` 를 기다리는" 확인을 거치지 않는다. `agent prompt --wait --until working` 이 실패하고 2초 뒤에도 `working` 이 아니면 곧바로 rc 4 다. 막 뜬 TUI 는 초기화 중이라 여기에 걸린다.
- **롤백**: `create_session.sh:264-271` 의 trap 은 `_herdr kill-session -t "$SESSION_NAME" 2>/dev/null || true` 로 실패를 삼킨다. kill 이 실패한 이유는 조사하지 않았다.
- **상태 불일치**: YAML 행(`atomic_dump_yaml`)과 레지스트리 등록(`delegate_submit_job`)이 주입보다 먼저 끝나므로, 롤백이 세션을 죽이더라도 YAML·레지스트리는 되돌려지지 않는다.
## 제안
1. 온보딩 주입도 래퍼와 같은 `started` 기반 확인(T = ack timeout)으로 판정한다. 가장 간단하게는 온보딩을 래퍼 `submit` 경로로 보낸다.
2. 롤백은 kill 결과를 확인한다. 실패하면 경고하고, 세션이 살아 있으면 "롤백 실패"를 별도 rc 로 알린다.
3. 롤백 시 YAML 행을 `error`/`terminated` 로 표시하거나 제거하고, 실패한 `delegate_job_id` 를 정리한다.
## 환경 정보
- MAM v4.5.0 (당시 main `5611859`), 현재 main `1134d74` 에서도 해당 코드 동일
- Linux, herdr 격리 세션 `multi-agent-mux`, claude Code v2.1.270
원인 후보: 호출자 herdr pane의 HERDR_WORKSPACE_ID가 격리 세션 조회 범위를 오염시킴
롤백 kill-session 실패의 원인 후보를 공유합니다. 같은 뿌리로 보이는 증상을 2026-09-15에 stop_session.sh에서 재현했습니다.
관찰
오케스트레이터(Claude Code)가 default herdr 서버의 pane 안에서 실행 중이었습니다. 그래서 환경에 HERDR_WORKSPACE_ID=w5(와 HERDR_PANE_ID, HERDR_TAB_ID)가 있었습니다. MAM 에이전트는 격리 세션 mam-gaia-exp의 workspace w18에 있습니다.
이 환경에서 source lib.sh 후 HERDR_SESSION_NAME=mam-gaia-exp herdr has-session -t <name>를 실행하면, 살아 있는 세션 3개 모두 rc=1이었습니다. 같은 명령을 env -u HERDR_WORKSPACE_ID로 실행하면 3개 모두 rc=0이었습니다.
stop_session.sh --session planner-creator-codex-01 --agent codex는 herdr already dead, just updating YAML을 출력하고 YAML을 stopped로 바꿨습니다. 그러나 codex는 계속 떠 있었고, status.sh는 YAML stopped / HERDR alive로 보고했습니다. HERDR_WORKSPACE_ID를 제거한 뒤에는 send-keys C-c와 kill-session이 정상 동작했습니다.
코드 경로 (main 1134d74)
_herdr_agent_get_scoped(lib.sh:339)는 두 번째 인자가 없으면 HERDR_WORKSPACE_ID를 범위로 씁니다. agent의 workspace_id가 이와 다르면 없는 것으로 처리합니다.
kill-session이 쓰는 _resolve_herdr_pane_id(lib.sh:395 부근)는 get_ws="${HERDR_WORKSPACE_ID:-}"로 위 함수를 부릅니다. 실패하면 pane list --workspace "$target_ws"를 _herdr_ws_scope(역시 HERDR_WORKSPACE_ID 우선) 값으로 거릅니다. 호출자의 workspace id(w5)는 격리 서버에 없는 값이라 pane을 찾지 못합니다.
그 결과 kill-session은 닫을 pane이 없어 아무것도 하지 않습니다. create_session.sh:268의 _herdr kill-session ... 2>/dev/null || true가 이를 삼키므로 "롤백했지만 세션은 살아 있음"이 됩니다.
lib.sh:549-553의 explicit-env miss 처리(ISSUE-3/F-2b)는 다른 workspace로 잘못 전달되는 것을 막으려는 의도입니다. 하지만 HERDR_SESSION_NAME이 호출자와 다른 서버를 가리키면, 호출자의 workspace id는 대상 서버에서 의미가 없습니다.
판별 방법
#23 재현 당시 오케스트레이터가 herdr pane 안에서 실행 중이었는지(env | grep HERDR_WORKSPACE_ID) 확인하면 이 후보가 맞는지 가릴 수 있습니다. 온보딩 전달 오판(send_keys_safe rc=4)은 별개 원인일 수 있습니다. 다만 agent prompt 대상 해석도 같은 범위 필터(lib.sh:549-553)를 거치므로 함께 확인할 만합니다.
제안
HERDR_SESSION_NAME이 호출자와 다른 서버를 가리킬 때는 HERDR_WORKSPACE_ID를 범위로 쓰지 않습니다. 또는 레지스트리의 herdr_workspace 라벨로 범위를 정합니다.
stop_session.sh는 "already dead"로 판단하기 전에 YAML pane.pid가 살아 있는지도 확인합니다. 둘이 어긋나면 YAML을 바꾸지 않고 경고합니다.
롤백의 kill-session은 결과를 확인합니다(본문 제안 2와 같음).
임시 우회: 오케스트레이터에서 MAM 스크립트를 env -u HERDR_WORKSPACE_ID -u HERDR_PANE_ID -u HERDR_TAB_ID로 실행합니다. status.sh는 이 문제의 영향을 받지 않았습니다.
## 원인 후보: 호출자 herdr pane의 `HERDR_WORKSPACE_ID`가 격리 세션 조회 범위를 오염시킴
롤백 `kill-session` 실패의 원인 후보를 공유합니다. 같은 뿌리로 보이는 증상을 2026-09-15에 `stop_session.sh`에서 재현했습니다.
### 관찰
- 오케스트레이터(Claude Code)가 default herdr 서버의 pane 안에서 실행 중이었습니다. 그래서 환경에 `HERDR_WORKSPACE_ID=w5`(와 `HERDR_PANE_ID`, `HERDR_TAB_ID`)가 있었습니다. MAM 에이전트는 격리 세션 `mam-gaia-exp`의 workspace `w18`에 있습니다.
- 이 환경에서 `source lib.sh` 후 `HERDR_SESSION_NAME=mam-gaia-exp herdr has-session -t <name>`를 실행하면, 살아 있는 세션 3개 모두 rc=1이었습니다. 같은 명령을 `env -u HERDR_WORKSPACE_ID`로 실행하면 3개 모두 rc=0이었습니다.
- `stop_session.sh --session planner-creator-codex-01 --agent codex`는 `herdr already dead, just updating YAML`을 출력하고 YAML을 `stopped`로 바꿨습니다. 그러나 codex는 계속 떠 있었고, `status.sh`는 YAML `stopped` / HERDR `alive`로 보고했습니다. `HERDR_WORKSPACE_ID`를 제거한 뒤에는 `send-keys C-c`와 `kill-session`이 정상 동작했습니다.
### 코드 경로 (main `1134d74`)
- `_herdr_agent_get_scoped`(lib.sh:339)는 두 번째 인자가 없으면 `HERDR_WORKSPACE_ID`를 범위로 씁니다. agent의 `workspace_id`가 이와 다르면 없는 것으로 처리합니다.
- `kill-session`이 쓰는 `_resolve_herdr_pane_id`(lib.sh:395 부근)는 `get_ws="${HERDR_WORKSPACE_ID:-}"`로 위 함수를 부릅니다. 실패하면 `pane list --workspace "$target_ws"`를 `_herdr_ws_scope`(역시 `HERDR_WORKSPACE_ID` 우선) 값으로 거릅니다. 호출자의 workspace id(`w5`)는 격리 서버에 없는 값이라 pane을 찾지 못합니다.
- 그 결과 `kill-session`은 닫을 pane이 없어 아무것도 하지 않습니다. create_session.sh:268의 `_herdr kill-session ... 2>/dev/null || true`가 이를 삼키므로 "롤백했지만 세션은 살아 있음"이 됩니다.
- lib.sh:549-553의 explicit-env miss 처리(ISSUE-3/F-2b)는 다른 workspace로 잘못 전달되는 것을 막으려는 의도입니다. 하지만 `HERDR_SESSION_NAME`이 호출자와 다른 서버를 가리키면, 호출자의 workspace id는 대상 서버에서 의미가 없습니다.
### 판별 방법
#23 재현 당시 오케스트레이터가 herdr pane 안에서 실행 중이었는지(`env | grep HERDR_WORKSPACE_ID`) 확인하면 이 후보가 맞는지 가릴 수 있습니다. 온보딩 전달 오판(`send_keys_safe` rc=4)은 별개 원인일 수 있습니다. 다만 `agent prompt` 대상 해석도 같은 범위 필터(lib.sh:549-553)를 거치므로 함께 확인할 만합니다.
### 제안
1. `HERDR_SESSION_NAME`이 호출자와 다른 서버를 가리킬 때는 `HERDR_WORKSPACE_ID`를 범위로 쓰지 않습니다. 또는 레지스트리의 `herdr_workspace` 라벨로 범위를 정합니다.
2. `stop_session.sh`는 "already dead"로 판단하기 전에 YAML `pane.pid`가 살아 있는지도 확인합니다. 둘이 어긋나면 YAML을 바꾸지 않고 경고합니다.
3. 롤백의 `kill-session`은 결과를 확인합니다(본문 제안 2와 같음).
임시 우회: 오케스트레이터에서 MAM 스크립트를 `env -u HERDR_WORKSPACE_ID -u HERDR_PANE_ID -u HERDR_TAB_ID`로 실행합니다. `status.sh`는 이 문제의 영향을 받지 않았습니다.
원인 후보 확인: macOS 에서 재현, delegate-job submit 사전 점검도 같은 원인으로 실패
위 댓글의 판별 방법대로 확인했습니다. 원인 후보가 맞았습니다.
환경
macOS (Darwin 25.6.0), MAM main 1134d74, herdr 격리 세션 mam-entry-math, --herdr-workspace mam-agents
오케스트레이터(Claude Code)는 default herdr 서버의 pane 안에서 실행 중이었습니다: HERDR_WORKSPACE_ID=wG, HERDR_TAB_ID=wG:t1, HERDR_PANE_ID=wG:p2
에이전트 3개(claude·agy·opencode)는 격리 세션의 workspace w2 에 있습니다 (.mam/herdr_workspace_id 도 w2)
관찰
create_session.sh --onboard 로 claude·opencode 를 만들자 본문과 같은 증상이 나왔습니다: send_keys_safe: delivery unknown/failed → instruction injection failed (prompt-lock, rc=4) → 롤백 메시지. 그러나 세션은 살아 있었고 YAML 은 running 이었습니다.
같은 환경에서 multi-agent-mux-delegate-job submit 으로 온보딩을 다시 보내자, 두 건 모두 preflight_agent 에서 에이전트 세션 '...'이 존재하지 않습니다 로 종료됐습니다 (cancelled). multi-agent-mux-delegate-job:916-918 의 herdr has-session 이 같은 범위 필터를 거치기 때문입니다.
env -u HERDR_WORKSPACE_ID 로 실행하면 3개 모두 has-session rc=0 이었습니다.
env -u HERDR_WORKSPACE_ID -u HERDR_TAB_ID -u HERDR_PANE_ID 로 submit 을 다시 보내자 3건 모두 started → completed 로 끝났습니다.
결론
롤백 kill-session 실패와 delegate-job submit 사전 점검 실패는 호출자의 HERDR_WORKSPACE_ID 누수가 원인으로 확인됩니다.
create_session.sh 의 온보딩 전달 오판(rc=4)은 env 를 뺀 상태로 create_session.sh 를 다시 실행해 보지는 않았습니다. 그래서 같은 원인이라고 단정하지는 않습니다. 다만 같은 환경에서 submit 경로가 모두 성공한 것으로 보아 같은 원인일 가능성이 높습니다.
영향 범위 추가
위 댓글의 목록에 multi-agent-mux-delegate-job 의 preflight_agent (916-918행)를 추가해야 합니다. 이 때문에 사용자가 온보딩을 수동으로 다시 보내는 경로도 막힙니다.
## 원인 후보 확인: macOS 에서 재현, `delegate-job submit` 사전 점검도 같은 원인으로 실패
위 댓글의 판별 방법대로 확인했습니다. 원인 후보가 맞았습니다.
### 환경
- macOS (Darwin 25.6.0), MAM main `1134d74`, herdr 격리 세션 `mam-entry-math`, `--herdr-workspace mam-agents`
- 오케스트레이터(Claude Code)는 default herdr 서버의 pane 안에서 실행 중이었습니다: `HERDR_WORKSPACE_ID=wG`, `HERDR_TAB_ID=wG:t1`, `HERDR_PANE_ID=wG:p2`
- 에이전트 3개(claude·agy·opencode)는 격리 세션의 workspace `w2` 에 있습니다 (`.mam/herdr_workspace_id` 도 `w2`)
### 관찰
1. `create_session.sh --onboard` 로 claude·opencode 를 만들자 본문과 같은 증상이 나왔습니다: `send_keys_safe: delivery unknown/failed` → `instruction injection failed (prompt-lock, rc=4)` → 롤백 메시지. 그러나 세션은 살아 있었고 YAML 은 `running` 이었습니다.
2. 같은 환경에서 `multi-agent-mux-delegate-job submit` 으로 온보딩을 다시 보내자, 두 건 모두 `preflight_agent` 에서 `에이전트 세션 '...'이 존재하지 않습니다` 로 종료됐습니다 (`cancelled`). `multi-agent-mux-delegate-job:916-918` 의 `herdr has-session` 이 같은 범위 필터를 거치기 때문입니다.
3. `bash -x .mam/shim/herdr has-session -t planner-creator-claude-01` 추적에서 `TARGET_WS=wG` 로 필터링되어 rc=1 이었습니다.
4. `env -u HERDR_WORKSPACE_ID` 로 실행하면 3개 모두 `has-session` rc=0 이었습니다.
5. `env -u HERDR_WORKSPACE_ID -u HERDR_TAB_ID -u HERDR_PANE_ID` 로 `submit` 을 다시 보내자 3건 모두 `started` → `completed` 로 끝났습니다.
### 결론
- 롤백 `kill-session` 실패와 `delegate-job submit` 사전 점검 실패는 호출자의 `HERDR_WORKSPACE_ID` 누수가 원인으로 확인됩니다.
- `create_session.sh` 의 온보딩 전달 오판(rc=4)은 env 를 뺀 상태로 `create_session.sh` 를 다시 실행해 보지는 않았습니다. 그래서 같은 원인이라고 단정하지는 않습니다. 다만 같은 환경에서 `submit` 경로가 모두 성공한 것으로 보아 같은 원인일 가능성이 높습니다.
### 영향 범위 추가
- 위 댓글의 목록에 `multi-agent-mux-delegate-job` 의 `preflight_agent` (916-918행)를 추가해야 합니다. 이 때문에 사용자가 온보딩을 수동으로 다시 보내는 경로도 막힙니다.
이 이슈의 뿌리는 세 겹이었고, 실제 herdr 시험을 돌리기 전에는 두 번째와 세 번째가 보이지 않았다.
1. 교차 서버 스코프. herdr pane 이 내보내는 HERDR_WORKSPACE_ID·HERDR_PANE_ID·HERDR_TAB_ID 는 발급한 서버에서만 의미가 있다. 다른 서버를 겨냥한 호출에서 이 값들을 제거한다. 호출자 서버는 HERDR_SOCKET_PATH 의 모양에서 구한다. 처음에는 HERDR_SESSION 으로 판정했는데, herdr 0.8.2 는 기본 서버의 pane 에 그 변수를 내보내지 않는다. 바로 그 pane 이 이 결함이 나오는 곳이라 수정이 통째로 무력이었다. 실측으로 확인했다.
cross-server verdict: NOT crossing (keeps w6)
has-session trial-agy: NOT FOUND
has-session trial-agy with env -u HERDR_WORKSPACE_ID/PANE_ID/TAB_ID: FOUND
2. 온보딩 전달 판정. 확인 조건이 started 이벤트의 data.dispatch_token 이었는데, 에이전트가 실행하는 publish_event.py 는 명령줄에 지시하지 않으면 그 필드를 쓰지 않는다. create 가 만드는 온보딩 명령에는 그 인자가 없었다. 조건이 절대 성립하지 않아, 온보딩을 완료한 에이전트도 180초를 기다린 뒤 미확인으로 판정됐고 이미 종료된 잡에 error 를 덮어쓰려 했다.
14:17:21 started (에이전트)
completed Onboarding complete; aligned with role trial
14:20:18 refused error for job 3fd52c76: conflicting error after terminal completed
14:20:18 WARNING: 'trial-agy' did not acknowledge the onboarding prompt within 180s.
delegate_publish_cmd 가 create 와 delegate-job 래퍼에 같은 발행 명령 모양을 주고, delegate_started_recorded 하나를 두 대기 지점이 공유한다. 원격 경로에서는 구독자가 HMAC 과 토큰을 검증한 뒤 dispatch_fingerprint(검증한 토큰의 SHA256)와 시도 번호를 남기고, 토큰을 요구하는 판정은 검증 표시와 지문 일치를 함께 요구한다. 신원 없는 과거 기록은 fail closed 다. 감사 파일이 같은 잡의 여러 시도에 걸쳐 이어 붙기 때문이다.
3. 실패 처리. 롤백이 kill 결과를 확인하고 has-session 으로 재확인한다. 죽이지 못했으면 행에 그대로 적고 exit 43 으로 끝낸다. 온보딩이 확인되지 않아도 세션은 보존한다(MAM_ONBOARD_ACK_S).
검증
전체 스위트 f4ba205: 1080 통과 / 12 실패 / 20 건너뜀. 실패 12건은 모두 이 호스트의 기존 기준선 실패이며 base 에서도 동일하다. 새 실패 없음. 리뷰어 5명 전원 PASS.
실제 herdr 수용 시험은 오케스트레이터 pane 의 상속 환경을 일부러 그대로 둔 채(env -u 우회 없이) 수행했다. agy·grok·codex·opencode 네 건 모두 사람 개입 없이 TUI 준비, 온보딩 프롬프트 전달, started(토큰 포함) → completed, 180초 경고 없음, 종료 코드 0.
남은 이야기
세 차례의 차단 지적이 모두 같은 뿌리였다. 테스트 픽스처가 실제 생산자와 달라서 결함을 가렸다. 2번 항목은 픽스처가 started 줄을 손으로 만들면서 토큰을 넣어 통과시키고 있었고, 그 수정의 새 테스트는 레코드를 pending 으로 되돌려 또 다른 우회를 가렸다. 픽스처는 이제 실제 registry.py 로 등록하고 실제 publish_event.py 로 발행한다. 마지막 지적은 리뷰어가 제출한 재현 자산을 수정 없이 돌려 확인했다 — 수정 전 2건 실패, 수정 후 5건 통과.
PR #35 로 해결했다. 브랜치 `fix/issue-23-24`, head `f4ba205`.
이 이슈의 뿌리는 세 겹이었고, 실제 herdr 시험을 돌리기 전에는 두 번째와 세 번째가 보이지 않았다.
**1. 교차 서버 스코프.** herdr pane 이 내보내는 `HERDR_WORKSPACE_ID`·`HERDR_PANE_ID`·`HERDR_TAB_ID` 는 발급한 서버에서만 의미가 있다. 다른 서버를 겨냥한 호출에서 이 값들을 제거한다. 호출자 서버는 `HERDR_SOCKET_PATH` 의 모양에서 구한다. 처음에는 `HERDR_SESSION` 으로 판정했는데, herdr 0.8.2 는 기본 서버의 pane 에 그 변수를 내보내지 않는다. 바로 그 pane 이 이 결함이 나오는 곳이라 수정이 통째로 무력이었다. 실측으로 확인했다.
```
cross-server verdict: NOT crossing (keeps w6)
has-session trial-agy: NOT FOUND
has-session trial-agy with env -u HERDR_WORKSPACE_ID/PANE_ID/TAB_ID: FOUND
```
**2. 온보딩 전달 판정.** 확인 조건이 `started` 이벤트의 `data.dispatch_token` 이었는데, 에이전트가 실행하는 `publish_event.py` 는 명령줄에 지시하지 않으면 그 필드를 쓰지 않는다. create 가 만드는 온보딩 명령에는 그 인자가 없었다. 조건이 절대 성립하지 않아, 온보딩을 **완료한** 에이전트도 180초를 기다린 뒤 미확인으로 판정됐고 이미 종료된 잡에 `error` 를 덮어쓰려 했다.
```
14:17:21 started (에이전트)
completed Onboarding complete; aligned with role trial
14:20:18 refused error for job 3fd52c76: conflicting error after terminal completed
14:20:18 WARNING: 'trial-agy' did not acknowledge the onboarding prompt within 180s.
```
`delegate_publish_cmd` 가 create 와 delegate-job 래퍼에 같은 발행 명령 모양을 주고, `delegate_started_recorded` 하나를 두 대기 지점이 공유한다. 원격 경로에서는 구독자가 HMAC 과 토큰을 검증한 뒤 `dispatch_fingerprint`(검증한 토큰의 SHA256)와 시도 번호를 남기고, 토큰을 요구하는 판정은 검증 표시와 지문 일치를 함께 요구한다. 신원 없는 과거 기록은 fail closed 다. 감사 파일이 같은 잡의 여러 시도에 걸쳐 이어 붙기 때문이다.
**3. 실패 처리.** 롤백이 kill 결과를 확인하고 `has-session` 으로 재확인한다. 죽이지 못했으면 행에 그대로 적고 exit 43 으로 끝낸다. 온보딩이 확인되지 않아도 세션은 보존한다(`MAM_ONBOARD_ACK_S`).
## 검증
전체 스위트 `f4ba205`: 1080 통과 / 12 실패 / 20 건너뜀. 실패 12건은 모두 이 호스트의 기존 기준선 실패이며 base 에서도 동일하다. 새 실패 없음. 리뷰어 5명 전원 PASS.
실제 herdr 수용 시험은 오케스트레이터 pane 의 상속 환경을 일부러 그대로 둔 채(`env -u` 우회 없이) 수행했다. agy·grok·codex·opencode 네 건 모두 사람 개입 없이 TUI 준비, 온보딩 프롬프트 전달, `started`(토큰 포함) → `completed`, 180초 경고 없음, 종료 코드 0.
## 남은 이야기
세 차례의 차단 지적이 모두 같은 뿌리였다. **테스트 픽스처가 실제 생산자와 달라서 결함을 가렸다.** 2번 항목은 픽스처가 `started` 줄을 손으로 만들면서 토큰을 넣어 통과시키고 있었고, 그 수정의 새 테스트는 레코드를 `pending` 으로 되돌려 또 다른 우회를 가렸다. 픽스처는 이제 실제 `registry.py` 로 등록하고 실제 `publish_event.py` 로 발행한다. 마지막 지적은 리뷰어가 제출한 재현 자산을 수정 없이 돌려 확인했다 — 수정 전 2건 실패, 수정 후 5건 통과.
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.
증상
2026-09-14 Linux 호스트에서
create_session.sh --onboard로 claude 세션 두 개(planner-reviewer-claude-01,creator-claude-01)를 만들 때 다음이 순서대로 일어났다.✅ claude TUI detected ready.뒤 온보딩 job 등록.send_keys_safe: delivery unknown/failed→ error 이벤트instruction injection failed (prompt-lock, rc=4)발행(job415e0daa,dc79c903).⚠️ Error occurred during initialization (exit code 1). Rolling back and killing herdr session ...출력, 스크립트 rc=1..mam/agent-sessions.yaml행은status: running으로 남았고delegate_job_id는 error 잡을 가리킨다(status.sh의 JOB_STATUS 열이 계속error).몇 분 뒤 같은 세션에 같은 온보딩 프롬프트를
multi-agent-mux-delegate-job submit으로 보내자 수 초 안에started→completed가 왔다. 즉 에이전트는 정상이었고, 판정만 틀렸다.원인
create_session.sh:558의inject_instructions→send_keys_safe경로는 #18 에서 래퍼에 들어간 "주입 후 T 초 안에started를 기다리는" 확인을 거치지 않는다.agent prompt --wait --until working이 실패하고 2초 뒤에도working이 아니면 곧바로 rc 4 다. 막 뜬 TUI 는 초기화 중이라 여기에 걸린다.create_session.sh:264-271의 trap 은_herdr kill-session -t "$SESSION_NAME" 2>/dev/null || true로 실패를 삼킨다. kill 이 실패한 이유는 조사하지 않았다.atomic_dump_yaml)과 레지스트리 등록(delegate_submit_job)이 주입보다 먼저 끝나므로, 롤백이 세션을 죽이더라도 YAML·레지스트리는 되돌려지지 않는다.제안
started기반 확인(T = ack timeout)으로 판정한다. 가장 간단하게는 온보딩을 래퍼submit경로로 보낸다.error/terminated로 표시하거나 제거하고, 실패한delegate_job_id를 정리한다.환경 정보
5611859), 현재 main1134d74에서도 해당 코드 동일multi-agent-mux, claude Code v2.1.270원인 후보: 호출자 herdr pane의
HERDR_WORKSPACE_ID가 격리 세션 조회 범위를 오염시킴롤백
kill-session실패의 원인 후보를 공유합니다. 같은 뿌리로 보이는 증상을 2026-09-15에stop_session.sh에서 재현했습니다.관찰
HERDR_WORKSPACE_ID=w5(와HERDR_PANE_ID,HERDR_TAB_ID)가 있었습니다. MAM 에이전트는 격리 세션mam-gaia-exp의 workspacew18에 있습니다.source lib.sh후HERDR_SESSION_NAME=mam-gaia-exp herdr has-session -t <name>를 실행하면, 살아 있는 세션 3개 모두 rc=1이었습니다. 같은 명령을env -u HERDR_WORKSPACE_ID로 실행하면 3개 모두 rc=0이었습니다.stop_session.sh --session planner-creator-codex-01 --agent codex는herdr already dead, just updating YAML을 출력하고 YAML을stopped로 바꿨습니다. 그러나 codex는 계속 떠 있었고,status.sh는 YAMLstopped/ HERDRalive로 보고했습니다.HERDR_WORKSPACE_ID를 제거한 뒤에는send-keys C-c와kill-session이 정상 동작했습니다.코드 경로 (main
1134d74)_herdr_agent_get_scoped(lib.sh:339)는 두 번째 인자가 없으면HERDR_WORKSPACE_ID를 범위로 씁니다. agent의workspace_id가 이와 다르면 없는 것으로 처리합니다.kill-session이 쓰는_resolve_herdr_pane_id(lib.sh:395 부근)는get_ws="${HERDR_WORKSPACE_ID:-}"로 위 함수를 부릅니다. 실패하면pane list --workspace "$target_ws"를_herdr_ws_scope(역시HERDR_WORKSPACE_ID우선) 값으로 거릅니다. 호출자의 workspace id(w5)는 격리 서버에 없는 값이라 pane을 찾지 못합니다.kill-session은 닫을 pane이 없어 아무것도 하지 않습니다. create_session.sh:268의_herdr kill-session ... 2>/dev/null || true가 이를 삼키므로 "롤백했지만 세션은 살아 있음"이 됩니다.HERDR_SESSION_NAME이 호출자와 다른 서버를 가리키면, 호출자의 workspace id는 대상 서버에서 의미가 없습니다.판별 방법
#23 재현 당시 오케스트레이터가 herdr pane 안에서 실행 중이었는지(
env | grep HERDR_WORKSPACE_ID) 확인하면 이 후보가 맞는지 가릴 수 있습니다. 온보딩 전달 오판(send_keys_saferc=4)은 별개 원인일 수 있습니다. 다만agent prompt대상 해석도 같은 범위 필터(lib.sh:549-553)를 거치므로 함께 확인할 만합니다.제안
HERDR_SESSION_NAME이 호출자와 다른 서버를 가리킬 때는HERDR_WORKSPACE_ID를 범위로 쓰지 않습니다. 또는 레지스트리의herdr_workspace라벨로 범위를 정합니다.stop_session.sh는 "already dead"로 판단하기 전에 YAMLpane.pid가 살아 있는지도 확인합니다. 둘이 어긋나면 YAML을 바꾸지 않고 경고합니다.kill-session은 결과를 확인합니다(본문 제안 2와 같음).임시 우회: 오케스트레이터에서 MAM 스크립트를
env -u HERDR_WORKSPACE_ID -u HERDR_PANE_ID -u HERDR_TAB_ID로 실행합니다.status.sh는 이 문제의 영향을 받지 않았습니다.원인 후보 확인: macOS 에서 재현,
delegate-job submit사전 점검도 같은 원인으로 실패위 댓글의 판별 방법대로 확인했습니다. 원인 후보가 맞았습니다.
환경
1134d74, herdr 격리 세션mam-entry-math,--herdr-workspace mam-agentsHERDR_WORKSPACE_ID=wG,HERDR_TAB_ID=wG:t1,HERDR_PANE_ID=wG:p2w2에 있습니다 (.mam/herdr_workspace_id도w2)관찰
create_session.sh --onboard로 claude·opencode 를 만들자 본문과 같은 증상이 나왔습니다:send_keys_safe: delivery unknown/failed→instruction injection failed (prompt-lock, rc=4)→ 롤백 메시지. 그러나 세션은 살아 있었고 YAML 은running이었습니다.multi-agent-mux-delegate-job submit으로 온보딩을 다시 보내자, 두 건 모두preflight_agent에서에이전트 세션 '...'이 존재하지 않습니다로 종료됐습니다 (cancelled).multi-agent-mux-delegate-job:916-918의herdr has-session이 같은 범위 필터를 거치기 때문입니다.bash -x .mam/shim/herdr has-session -t planner-creator-claude-01추적에서TARGET_WS=wG로 필터링되어 rc=1 이었습니다.env -u HERDR_WORKSPACE_ID로 실행하면 3개 모두has-sessionrc=0 이었습니다.env -u HERDR_WORKSPACE_ID -u HERDR_TAB_ID -u HERDR_PANE_ID로submit을 다시 보내자 3건 모두started→completed로 끝났습니다.결론
kill-session실패와delegate-job submit사전 점검 실패는 호출자의HERDR_WORKSPACE_ID누수가 원인으로 확인됩니다.create_session.sh의 온보딩 전달 오판(rc=4)은 env 를 뺀 상태로create_session.sh를 다시 실행해 보지는 않았습니다. 그래서 같은 원인이라고 단정하지는 않습니다. 다만 같은 환경에서submit경로가 모두 성공한 것으로 보아 같은 원인일 가능성이 높습니다.영향 범위 추가
multi-agent-mux-delegate-job의preflight_agent(916-918행)를 추가해야 합니다. 이 때문에 사용자가 온보딩을 수동으로 다시 보내는 경로도 막힙니다.PR #35 로 해결했다. 브랜치
fix/issue-23-24, headf4ba205.이 이슈의 뿌리는 세 겹이었고, 실제 herdr 시험을 돌리기 전에는 두 번째와 세 번째가 보이지 않았다.
1. 교차 서버 스코프. herdr pane 이 내보내는
HERDR_WORKSPACE_ID·HERDR_PANE_ID·HERDR_TAB_ID는 발급한 서버에서만 의미가 있다. 다른 서버를 겨냥한 호출에서 이 값들을 제거한다. 호출자 서버는HERDR_SOCKET_PATH의 모양에서 구한다. 처음에는HERDR_SESSION으로 판정했는데, herdr 0.8.2 는 기본 서버의 pane 에 그 변수를 내보내지 않는다. 바로 그 pane 이 이 결함이 나오는 곳이라 수정이 통째로 무력이었다. 실측으로 확인했다.2. 온보딩 전달 판정. 확인 조건이
started이벤트의data.dispatch_token이었는데, 에이전트가 실행하는publish_event.py는 명령줄에 지시하지 않으면 그 필드를 쓰지 않는다. create 가 만드는 온보딩 명령에는 그 인자가 없었다. 조건이 절대 성립하지 않아, 온보딩을 완료한 에이전트도 180초를 기다린 뒤 미확인으로 판정됐고 이미 종료된 잡에error를 덮어쓰려 했다.delegate_publish_cmd가 create 와 delegate-job 래퍼에 같은 발행 명령 모양을 주고,delegate_started_recorded하나를 두 대기 지점이 공유한다. 원격 경로에서는 구독자가 HMAC 과 토큰을 검증한 뒤dispatch_fingerprint(검증한 토큰의 SHA256)와 시도 번호를 남기고, 토큰을 요구하는 판정은 검증 표시와 지문 일치를 함께 요구한다. 신원 없는 과거 기록은 fail closed 다. 감사 파일이 같은 잡의 여러 시도에 걸쳐 이어 붙기 때문이다.3. 실패 처리. 롤백이 kill 결과를 확인하고
has-session으로 재확인한다. 죽이지 못했으면 행에 그대로 적고 exit 43 으로 끝낸다. 온보딩이 확인되지 않아도 세션은 보존한다(MAM_ONBOARD_ACK_S).검증
전체 스위트
f4ba205: 1080 통과 / 12 실패 / 20 건너뜀. 실패 12건은 모두 이 호스트의 기존 기준선 실패이며 base 에서도 동일하다. 새 실패 없음. 리뷰어 5명 전원 PASS.실제 herdr 수용 시험은 오케스트레이터 pane 의 상속 환경을 일부러 그대로 둔 채(
env -u우회 없이) 수행했다. agy·grok·codex·opencode 네 건 모두 사람 개입 없이 TUI 준비, 온보딩 프롬프트 전달,started(토큰 포함) →completed, 180초 경고 없음, 종료 코드 0.남은 이야기
세 차례의 차단 지적이 모두 같은 뿌리였다. 테스트 픽스처가 실제 생산자와 달라서 결함을 가렸다. 2번 항목은 픽스처가
started줄을 손으로 만들면서 토큰을 넣어 통과시키고 있었고, 그 수정의 새 테스트는 레코드를pending으로 되돌려 또 다른 우회를 가렸다. 픽스처는 이제 실제registry.py로 등록하고 실제publish_event.py로 발행한다. 마지막 지적은 리뷰어가 제출한 재현 자산을 수정 없이 돌려 확인했다 — 수정 전 2건 실패, 수정 후 5건 통과.