release(v4.9.6): opencode 2.x only (Issue #60) - archive the review reports, bump the lockstep version

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-10-18 19:35:48 +09:00
1 parent 0e05b1b677
commit cb2c84ccd7
29 files changed
+835 -25

No files matched your search

@@ -0,0 +1,14 @@
{
"loop_id": "loop-20261018T103110Z-8a6969",
"base_sha": "74333f87d0c02888c2b5b281e912d97e74348c01",
"head_sha": "0e05b1b6777ceb837066454ac688e04a0f5d14a0",
"tree_sha": "c5cb1b16af283b3fdb447bafb865ba1abbfbb5a8",
"review_commit": "0e05b1b6777ceb837066454ac688e04a0f5d14a0",
"reviewer": "planner-reviewer-codex-01",
"job_id": "598fe58e",
"verdict": "PASS",
"transport": "herdr",
"evidence": "review-tree",
"isolation": "worktree",
"verified_at": "2026-10-18T10:34:16Z"
}
@@ -0,0 +1,17 @@
# Issue #60, review 3 (head 0e05b1b)
**Scope and identity.** Reviewed base `74333f87d0c02888c2b5b281e912d97e74348c01` through head `0e05b1b6777ceb837066454ac688e04a0f5d14a0` in the assigned review-only worktree. Its HEAD/tree matched the brief and `git status --short` was empty. No code or branch changes were made.
**Review result.** The second-round isolation defect is addressed: `tests/_agent_home_guard.py:21` now drops inherited `XDG_DATA_HOME` for each test, and `:48-49` snapshots the OpenCode directory under the ambient XDG root so data written there is still detected. `tests/test_issue60_opencode_v2.py:297-304` probes `--version` with a temporary `HOME` and `TMPDIR`; the opt-in real 2.x contract test also uses its own temporary home and standalone API processes (`:311-352`). I ran that contract test here; it passed, with no service operation or model prompt. The worktree had no changes after testing, and process inspection showed only the pre-existing user `opencode serve --service` process (PID 86524), which was left untouched.
The status change preserves the prior `yes`/`MISSING`/`no` behavior while resolving the database through the adapter and querying `session_v2`: `.agents/skills/multi-agent-mux-status/scripts/status.sh:105-121`. The same branch exists in both embedded status paths. This avoids the stale fixed `~/.local/share` lookup and does not change status formatting or exit handling. Adapter path resolution at `.agents/skills/lib_py/agents/adapters/opencode.py:90-123` applies caller XDG data only to the caller's own home, runs `debug paths` with the requested home, and falls back to that home/XDG-derived 2.x database path.
No blocking defect found in the cumulative change. The prior data-safety and purge concerns remain covered: focused tests exercise parent/child deletion, legacy rows retained by migrated databases, preservation of unrelated sessions, and residual reporting after a failed delete. `discover` selects top-level `session_v2` rows and verifies child sessions independently. The fake CLI and real 2.x opt-in test both cover the expected v2 contract. Search of active skills found no remaining OpenCode assumptions on `db path`, `FROM session`, or an OpenCode `--agent` option; only explanatory comments and generic delegation syntax matched.
**Validation.** On macOS / Python 3.9.6, ran the related suite in the review worktree with a temporary `HOME`/`TMPDIR`, XDG variables unset, requested environment scrubbed, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, and an external temporary basetemp:
`tests/test_issue60_opencode_v2.py tests/test_a4_adapter_contract.py tests/test_issue31_purge_residual.py tests/test_issue55_agent_homes.py tests/test_issue42_skill_blocks.py tests/test_issue10_13_purge.py tests/test_tier2_component.py::test_comp_opencode_create_and_reconcile_and_stop`
Result: **187 passed, 1 skipped** (118.14s). Also ran `git diff --check` over base..head and `bash -n` on the modified status, reconcile, create, and resume shell scripts; both passed. The full suite and Linux runtime were not rerun in this reviewer turn; the brief records the orchestrator's full-suite result (1949 passed, 56 skipped). No Linux-specific failure was observed, but this review's runtime validation was macOS-only.
[VERDICT: PASS]
@@ -0,0 +1,100 @@
# Issue #60 — OpenCode 2.x Mac review
**NOT PASS.** 기본 2.x 실행·조회·삭제 계약과 테스트는 통과했다. 그러나 새 재귀 삭제 경로가 자식 세션의 구버전 데이터를 남긴 뒤 부모 purge의 residual을 빈 목록으로 보고하는 결함을 확인했다. 실제 OpenCode 2.0.32에서도 재현됐다. 국소 수정과 회귀 테스트로 해결할 수 있으므로 재계획 요청은 없다.
## Identity / 범위
- Job `614b6263`, loop `loop-20261018T055915Z-abe16e`
- Base `74333f87d0c02888c2b5b281e912d97e74348c01`
- Head `6b4b96fa1875862ef3bed830fe7b04166b1fad58`
- Tree `9cfb2106081eba96b58cc6b7573031de0acba399`
- 검증 트리 `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261018T055915Z-abe16e/1`
- macOS, 시스템 Python 3.9.6, bash 3.2, 실제 OpenCode `v2.0.32`.
시작/종료 HEAD와 tree가 일치하고 `git status --short`는 비어 있다. creator 트리 실행·수정, 제품 코드 수정, 브랜치/커밋 변경 없음. 추가 탐침과 의도적 회귀는 검증 HEAD의 임시 git archive 복사본에서만 수행했다. `tea issues 60 --login pugit --repo tmpl/multi-agent-mux --comments`로 본문과 정정 댓글도 읽었다.
## 수정 필요 — P2: 자식의 legacy 데이터가 남아도 부모 purge는 residual 없음
**파일 근거:** `.agents/skills/lib_py/agents/adapters/opencode.py:141`~`:145`는 네이티브 CLI에 부모와 자식들의 삭제를 맡기고 이를 성공으로 보고한다. 이어지는 `:158`~`:169`의 legacy SQL은 요청한 **부모 uuid 하나**만 삭제한다. `:188`~`:193`의 residual도 그 uuid 하나만 조회한다.
**재현 절차:**
1. HOME/TMPDIR가 임시이고 XDG_*가 unset인 환경에서 실제 2.0.32의 `api --standalone POST /api/session -d ...`로 부모 P, `parentID=P`인 자식 C, 무관한 세션 O를 만든다. 모델 프롬프트는 보내지 않는다.
2. 이 임시 DB에 마이그레이션 잔존 상태를 모형화하는 legacy `session(id,parent_id)`와 `message(id,session_id)` 테이블을 만들고 P/C/O에 해당하는 행을 넣는다. 실제 사용자 DB나 실제 migration 파일을 수정한 것이 아니다.
3. HEAD 어댑터로 `purge_artifacts(P, ctx)` 후 `residual_artifacts(P, ctx)`를 호출한다.
4. session_v2, legacy session/message, C의 residual을 읽는다.
**기대:** 삭제 범위를 자식까지 확장했으므로 C의 소유 legacy 데이터도 정리하거나, 최소한 남은 C 데이터를 부모 purge 결과의 residual에 포함해야 한다. 무관한 O는 유지해야 한다.
**실제:**
- session_v2에는 O만 남아 네이티브 부모/자식 삭제는 성공했다.
- legacy session에는 **C와 O**가 남았고 legacy message의 C 행도 남았다.
- purged에는 `opencode session (with its child sessions): P`, `sqlite rows for session: P`가 출력됐다.
- **부모 residual은 `[]`**였다. C를 별도로 조회해야 `session.id: 1 rows`, `message.session_id: 1 rows`가 드러났다.
- 같은 상황을 저장소 fake CLI로 시험한 추가 테스트도 실패했다. 즉 fake와 실제 CLI의 불일치가 아니라 어댑터의 자식 범위 추적 누락이다.
실제 재현 ID와 결과는 `/tmp/review-614b6263-c_7f_2xc/real-probe/result.json`, 재현 스크립트는 `real_probe.py`에 있다. 스크립트는 위 재현에 이어 독립 TUI 종료 확인까지 수행한다. `probe-copy/tests/test_issue60_opencode_v2.py::test_review_child_legacy_rows_are_not_silently_abandoned`에도 작은 fake 기반 재현이 있다.
**수정 방향:** 네이티브 삭제 전에 session_v2의 해당 부모 아래 자손 ID 집합을 확보하고, 삭제 후 legacy 정리와 residual 검증을 그 정확한 집합에 적용한다. 보존 대상 테이블의 자식 잔여물도 누락하지 않아야 한다. 실패/부분 실패 시에도 범위를 잃지 않고 WARN/residual을 남긴다. prefix나 workspace만으로 범위를 넓히면 다른 세션을 지우므로 피한다. 부모·자식·손자·무관한 세션이 있는 migrated DB 회귀 테스트를 추가한다.
## 기존 코드에서 이어지는 호환성 주의점
다음 두 가지는 추가 탐침으로 확인했지만 **이번 diff가 새로 도입한 결함으로 분류하지 않았다**. 위 자식 residual 결함만으로 NOT PASS이며, 아래 사항은 Issue #60/Linux 후속 검증에서 명시적으로 다룰 필요가 있다.
### XDG_DATA_HOME 지정 시 실제 세션 저장소를 놓침
`opencode.py:80`~`:84`에서 호출 환경의 XDG_DATA_HOME을 항상 `<ctx.home_dir>/.local/share`로 덮어쓴다. debug paths를 사용해도 이미 다른 환경으로 질의하므로 호출자의 XDG 경로를 발견하지 못한다. status.sh의 `resume_on_disk`도 기본 HOME 경로를 직접 쓴다.
임시 홈 H와 임시 `XDG_DATA_HOME=D`를 두고 fake CLI가 따르는 `D/opencode/opencode.db`에만 ses_xdg를 넣었다. 기대 discover는 `[ses_xdg]`이지만 실제 `_resolve_db`는 H/.local/share/opencode/opencode.db, discover는 `[]`였다. 이 시험에서 custom XDG를 받은 실행 파일은 **저장소 fake뿐**이며 실제 바이너리는 실행하지 않았다. 원본 이전 구현도 같은 강제 override를 하므로 신규 회귀라고 주장하지 않는다.
수정 시 대상 홈 격리 계약을 유지하면서 같은 사용자 환경의 명시적 XDG_DATA_HOME을 존중하고, spawn/discovery/status/purge가 동일한 data root를 쓰도록 해야 한다. 별도 홈을 지정할 때 ambient XDG가 실제 홈으로 새는 문제도 함께 막아야 한다. 이슈 정정 댓글의 XDG 경로 요구와 현재 동작에는 이 차이가 남아 있다.
### 공유 DB의 열린 FD가 모든 세션을 in-use로 만듦
OpenCode는 `base.py:307`~`:309`의 공통 판정을 상속하고, artifact_path가 세션 전용 파일이 아닌 전체 opencode.db다. `base.py:78`~`:83`의 lsof 결과는 어떤 프로세스든 그 DB를 열면 True다.
임시 DB의 종료된 세션에 대해 먼저 in-use=False를 확인한 뒤, UUID 인자 없이 DB 파일만 읽기 모드로 열고 잠드는 Python 프로세스를 만들었다. 같은 세션의 in-use가 True가 됐다. holder는 finally에서 kill/wait했다. 따라서 다른 standalone TUI나 사용자 서비스가 공용 DB를 열고 있으면 종료된 세션도 purge gate에서 거절될 수 있다. 실제 사용자 서비스에 대해 in-use나 DB API를 호출해 검증하지는 않았다. 공유 파일 기반 판정은 base에도 있던 동작이며 이번 변경의 신규 회귀는 아니다.
후속 수정은 단순히 안전 검사를 제거하기보다 OpenCode 세션별 보유/실행 상태 증거를 사용해야 한다. `--standalone`은 서버 생명주기를 분리하지만 DB를 세션마다 분리하는 옵션은 아니라는 점이 중요하다.
## 검증 결과
### 원본 HEAD: 181 passed, 1 skipped
실행 모듈:
- tests/test_issue60_opencode_v2.py
- tests/test_a4_adapter_contract.py
- tests/test_issue31_purge_residual.py
- tests/test_issue42_skill_blocks.py
- tests/test_issue55_agent_homes.py
- tests/test_issue10_13_purge.py
**181 passed, 1 skipped in 53.41s**, pytest exit 0. skip은 worktree에 untracked `.claude/skills`가 없어 `test_issue42_skill_blocks.py:266`을 건너뛴 것이다. 실제 OpenCode 2.x 계약 테스트는 skip되지 않고 통과했다. 전체 스위트는 오케스트레이터 범위라 실행하지 않았다.
원본 검증은 지정 venv의 `python -m pytest -p no:cacheprovider --basetemp=/tmp/review-614b6263-c_7f_2xc/pytest-head -q -ra ...`, `PYTHONDONTWRITEBYTECODE=1`로 수행했다. HERDR/MAM/WORKSPACE 바인딩을 제거하고 HOME/TMPDIR를 임시화, 실제 바이너리 계약 테스트는 XDG_DATA_HOME/STATE_HOME/CONFIG_HOME/CACHE_HOME unset으로 수행했다. Claude 관련 테스트의 실제 /tmp fallback도 격리했다.
### 추가 검증
- 추가 edge 탐침 **3 failed, 10 deselected in 2.23s**: 자식 legacy residual 누락, XDG 경로 누락, 공유 DB FD false positive. 원본 제공 스위트의 실패로 합산하지 않는다.
- discover의 SQL만 임시 복사본에서 session_v2→session으로 되돌린 대조군: **2 failed, 11 deselected in 1.30s**. 기존 `test_discover_and_verify_read_session_v2_and_skip_child_sessions`, `test_a_fresh_2x_database_has_no_session_table_and_still_works`가 빈 결과를 거부했다. 원본은 변경하지 않았고 대조군 수정도 복원했다.
- 가짜 herdr/가짜 OpenCode를 쓰는 추가 전체 연결 시험: **1 passed in 45.07s**. 실제 create_session.sh 실행 → mock DB에 parent/더 최신 child 구성 및 own ID 비우기 → reconcile.sh --once에서 drift C가 parent만 확정 → status.sh --json의 resume_state=yes → stop_session.sh로 stopped → resume_session.sh의 `--session <동일 parent> --auto --standalone` 확인. 첫 모델 메시지 대신 mock DB seed를 사용했으며 실제 프롬프트나 실제 herdr 서버는 쓰지 않았다. fixture 안의 모의 온보딩 잡만 생성됐다.
- 실제 2.0.32: 임시 홈에서 API 부모/자식 생성 및 삭제, 무관한 세션 보존 확인. 별도 PTY에서 살아남은 세션을 `--session ... --auto --standalone`으로 재개하고 `/exit` 입력 후 0.3초 간격을 두어 Enter 전송, **exit 0** 확인. 모델 프롬프트 없음.
- 시험 후 프로세스 확인: 기존 사용자 OpenCode 서비스 PID 86524만 남았다. 그 프로세스에는 제어 명령을 보내지 않았으며 시험으로 만든 OpenCode 프로세스는 남지 않았다.
- 변경 Python 파일 모두 시스템 Python 3.9 AST 검사 통과. 변경 셸 파일 모두 `/bin/bash -n` 통과. base..head `git diff --check` 통과.
## 코드/문서 검토 결론과 한계
- session_v2.directory/time_created(ms), discover의 parent_id IS NULL 필터는 새 subagent가 본 세션을 덮어쓰는 것을 피하며 새 DB와 migrated DB 가정에 맞는다.
- `debug paths`의 db 줄 파싱, `session delete --standalone -- <id>`, 실패/바이너리 부재 WARN 및 부모 residual, 없는 ID에 대한 불필요 CLI 호출 회피는 관련 테스트에서 확인됐다.
- create/resume/update_yaml의 플래그와 adapter 생성 명령은 일치한다. active `.agents/skills`, docs, README 검색에서 운영 명령으로 남은 `opencode db path`나 TUI `--agent` 사용은 찾지 못했다. 설명용 과거 동작 언급, 의도적인 legacy DELETE, `opencode run -m`은 해당하지 않는다. VERSIONS/과거 리포트는 판단에서 제외했다.
- fake CLI는 기본 db path 및 v2 재귀 삭제/legacy 보존을 실제 계약과 일치하게 모형화한다. 다만 mock lifecycle은 실제 TUI의 모든 시그널 동작을 증명하지 않는다. 실제 PTY에서는 정상 `/exit` 경로를 확인했고 실제 SIGKILL/SIGHUP 전 조합을 반복 검증하지 않았다.
- auth_ok와 auth.json 검사는 브리프의 의도된 제외 범위로 취급했다.
- Linux에서 실행한 결과는 없다. Python 3.9/bash 3.2 구문과 플랫폼 전용 새 의존성 여부를 검토했지만 Linux 런타임, 서비스/PTY 동작은 예정된 별도 검증이 필요하다.
## 증거
임시 루트 `/tmp/review-614b6263-c_7f_2xc` (실경로 `/private/tmp/...`): `head.log`, `probes.log`, `mutation.log`, `flow.log`, `real_probe.py`, `real-probe/result.json`, `real-probe/tui-output.bin`, `processes-after.json`. 추가 pytest 재현은 `probe-copy/tests/test_issue60_opencode_v2.py` 및 `probe-copy/tests/test_review60_flow.py`에 있다.
[VERDICT: NOT PASS]
@@ -0,0 +1,120 @@
# Issue #60 2차 리뷰 — NOT PASS
이전 자식 legacy 데이터 누락은 실제 OpenCode 2.0.32에서도 해결됐다. 새 기능의 행동 테스트와 수정 전 실패도 확인했다. 그러나 새 XDG_DATA_HOME 읽기가 테스트의 환경 격리 목록에 반영되지 않아 원본 HEAD 검사 1건이 실패하며, 실제로 sandbox 밖 DB 경로를 선택하는 회귀가 있다. 국소 수정이 필요하고 재계획은 필요하지 않다.
## Identity
- Job: `c4e96018`; loop: `loop-20261018T064723Z-79133f`
- Base: `74333f87d0c02888c2b5b281e912d97e74348c01`
- Head: `cb5f439cce41a2741a6b9974a308a765e50f00ad`
- Tree: `cfec95403cbde10a6340b9007e1246c83be461cd`
- 검증 경로: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261018T064723Z-79133f/1`
시작/종료의 HEAD/tree가 일치하고 검증 트리는 clean이다. creator 트리에서 실행·수정하지 않았다. 제품 원본과 원본 테스트는 수정하지 않았다. 아래 대조군만 검증 트리의 별도 git archive 복사본에서 변경했다. 판정은 base..head 누적 변경을 대상으로 한다.
## 수정 필요 — P1: 상속된 XDG_DATA_HOME이 테스트 sandbox 밖을 가리킴
**근거:** `.agents/skills/lib_py/agents/adapters/opencode.py:90`~`:93`은 HOME과 ctx.home_dir가 같으면 ambient XDG_DATA_HOME을 반환하도록 새로 변경됐다. 그런데 `tests/_agent_home_guard.py`의 `DROPPED_VARS`에는 XDG_DATA_HOME이 없고, `tests/conftest.py::_isolate_agent_homes`는 그 목록만 제거한다. `mam_sandbox`는 HOME/HOME_DIR를 임시 홈으로 바꾸지만 XDG_DATA_HOME은 제거하지 않는다. 따라서 이 시점에 own=True가 되면서 호출자의 외부 XDG data root가 그대로 테스트에 유입된다. `watch_dirs()` 역시 opencode의 기본 HOME/.local/share 경로만 감시한다.
**원본 HEAD에서 관측:** `tests/test_issue55_agent_homes.py:61`의 `test_l1_every_path_variable_the_code_reads_is_accounted_for`가 다음 값으로 실패했다.
```text
path variables the adapters/lib.sh read but conftest._isolate_agent_homes does not drop: ['XDG_DATA_HOME']
```
이는 새 파일 생성에 대한 외부 에이전트 활동 오탐이 아니라, 소스 코드가 읽는 변수와 격리 목록의 결정적 불일치다. 테스트 프로세스 시작 전에 XDG를 unset한 이번 리뷰 환경에서도 목록 검사는 실패한다.
**안전한 재현 절차:**
1. 검증 HEAD 복사본에 `mam_sandbox` fixture를 사용하는 짧은 pytest 테스트를 둔다.
2. pytest 호출자의 HOME은 임시 H, XDG_DATA_HOME은 별개의 임시 D로 설정한다. 실제 사용자 경로는 사용하지 않는다.
3. 테스트 안에서 OpenCode 어댑터의 `_bin`을 None으로 대체해 실제 CLI 실행을 막고, `DiscoveryContext(workspace=sandbox, agent_name='opencode', home_dir=sandbox)`로 `_resolve_db`를 호출한다.
4. 결과가 sandbox 내부인지 검사한다.
**기대:** 각 테스트가 명시적으로 설정하지 않은 ambient XDG는 격리되어 `<sandbox>/.local/share/opencode/opencode.db`가 선택돼야 한다.
**실제:**
```text
sandbox: /private/tmp/review-c4e96018-i02oz_61/pytest-isolation/test_inherited_data_home_does_0
resolved_db: /tmp/review-c4e96018-i02oz_61/ambient-data/opencode/opencode.db
```
추가 탐침은 **1 failed in 0.09s**. 실제 파일 쓰기나 삭제를 하지 않고 경로 선택만 확인했다. 일반 개발자 환경에서 D가 실제 XDG 디렉터리라면 어댑터/테스트가 그 DB를 읽거나 변경할 수 있으므로, 단순한 목록 테스트의 기대값 갱신으로 넘길 문제는 아니다. 실제 사용자 데이터 훼손이 관측됐다는 뜻은 아니다.
**수정 방향:** `DROPPED_VARS`에 XDG_DATA_HOME을 추가하여 테스트마다 제거하고, XDG 동작을 검증하는 테스트만 명시적으로 임시 XDG를 설정하게 한다. 세션 guard의 ambient 변수 기록 및 `watch_dirs()`도 새 data-root 계약을 반영하여 실제 XDG 경로를 감시하게 한다. `LEFT_ALONE`에만 추가하거나 목록 단언을 약화하면 sandbox 경로 문제는 남는다.
**대조군:** 임시 복사본에서 DROPPED_VARS에 XDG_DATA_HOME만 추가하자 위 경로 탐침과 기존 L1 검사 두 건이 **2 passed in 0.09s**로 바뀌었다. 이는 격리 누락 원인의 확인이며, 원본 수정이나 전체 검증 완료를 의미하지 않는다. 감시 경로 보완도 함께 필요하다.
## 이전 지적 및 이번 변경 확인
### 자식 legacy 데이터 삭제와 residual
`purge_artifacts`가 CLI 호출 전에 `_descendants`로 session_v2와 legacy session의 parent_id를 따라 모든 깊이의 ID를 모은다. 중복/순환은 found와 root 비교로 제한한다. legacy message/messages/part/parts/todo/session_share/session/sessions를 해당 ID 집합에 대해 삭제한다. 관련 없는 세션은 집합에 넣지 않는다.
이전 리뷰의 실제 재현을 새로운 임시 홈에서 반복했다. 실제 2.0.32 standalone API로 부모 P, 자식 C, 무관한 O를 생성하고, 임시 DB에 P/C/O의 legacy session/message를 추가한 뒤 현재 어댑터로 P를 purge했다.
- session_v2와 legacy session 모두 **O만 남았다**.
- P와 C의 residual은 각각 `[]`였다.
- purged는 부모/자식 네이티브 삭제와 `and 1 child session(s)` legacy 정리를 보고했다.
- 원래 차단 지적이었던 C의 legacy 행 생존은 재현되지 않았다.
새 테스트는 손자 포함 삭제, 무관한 세션 보존, 삭제 실패 시 `[child session ...]` residual도 확인한다. todo의 변경된 계약은 issue31 테스트가 삭제됨을 단언하며 통과했다. session_share도 구현의 명시적 legacy 집합에 포함됐다.
### 서비스 보유자 / XDG / resident / drift C
- 공유 DB에 대한 서비스 보유자 제외, 일반 보유자 및 UUID 명령행 보유자 유지 테스트 통과.
- 호출자 자신의 HOME에서 XDG_DATA_HOME 존중, 다른 HOME에서는 그 홈의 기본 data root 사용 테스트 통과. 제품 경로 계약은 의도대로 바뀌었으나 위 테스트 격리 갱신이 누락됐다.
- resident_children=('opencode',) 테스트 통과. standalone 서버 자체는 idle 판단에서 제외하지만 그 아래 작업 프로세스는 계속 탐색하여 작업으로 본다.
- `test_comp_opencode_create_and_reconcile_and_stop` 통과. 더 최신 자식이 있어도 실제 reconcile.sh 경로가 부모를 확정한다. 이는 문자열 검사만이 아니라 임시 fixture 안의 행동 검증이다.
- 실제 바이너리 계약 테스트는 `_bin`을 발견한 바이너리로 고정하여 임시 HOME에서 PATH에 의존하지 않도록 바뀌었고 실제 2.0.32로 통과했다.
**수정 전 대조군:** 현재 `test_issue60_opencode_v2.py`를 이전 HEAD `6b4b96f`의 별도 archive에 넣어 아래 다섯 노드만 실행했다. 모두 수정 전 동작 때문에 실패했다(**5 failed, 10 deselected in 2.57s**).
1. test_purge_takes_the_1x_rows_of_the_child_sessions_too
2. test_a_failed_delete_reports_the_child_sessions_left
3. test_the_background_service_alone_does_not_hold_a_session
4. test_the_data_dir_follows_the_callers_xdg_data_home_only_for_its_own_home
5. test_the_standalone_server_is_a_resident_child_but_its_tools_are_work
같은 다섯 노드는 현재 HEAD에서 통과했다. drift-C 필터 제거 변형은 이번 리뷰에서 별도로 반복하지 않았으며 현재 행동 테스트 통과를 확인했다.
## 원본 HEAD 실행 결과
다음 6개 모듈과 tier2 노드 1개 실행:
- tests/test_issue60_opencode_v2.py
- tests/test_a4_adapter_contract.py
- tests/test_issue31_purge_residual.py
- tests/test_issue42_skill_blocks.py
- tests/test_issue55_agent_homes.py
- tests/test_issue10_13_purge.py
- tests/test_tier2_component.py::test_comp_opencode_create_and_reconcile_and_stop
**1 failed, 186 passed, 1 skipped in 91.16s**, pytest exit 1. 유일한 실패는 위 XDG 변수 목록 검사다. skip은 untracked `.claude/skills`가 검증 worktree에 없는 issue42 검사이며, 실제 OpenCode 테스트는 skip되지 않았다.
변경 Python 파일의 시스템 Python 3.9 AST 구문 검사, 변경 셸 파일의 `/bin/bash -n`, base..head `git diff --check` 모두 통과. 전체 스위트는 오케스트레이터가 별도로 실행하는 범위라 반복하지 않았다.
## 안전 범위와 남은 제한
- 지정 venv, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, 검증 트리 밖 basetemp로 실행했다. HERDR/MAM/WORKSPACE/CLAUDE_CONFIG_DIR 바인딩은 제거했다.
- 실제 바이너리는 HOME/TMPDIR 임시화, XDG_* unset, 서버 사용 명령은 --standalone으로만 실행했다. `_run`이 설정하는 XDG_DATA_HOME도 해당 임시 홈 아래다. 모델 프롬프트나 실제 사용자 서비스/API 호출은 하지 않았다.
- XDG ambient 재현에는 임시 경로와 `_bin=None`만 사용했다. 실제 opencode 데이터나 실제 DB는 건드리지 않았다.
- 종료 후 OpenCode 프로세스는 기존 사용자 서비스 PID 86524만 남았다. 그 서비스는 제어하지 않았다. 시험 프로세스가 남지 않았다.
- 1차 리뷰에서 확인한 create→drift C→status→stop→resume 연결 및 실제 PTY의 `/exit` 성공 근거는 `.mam/jobs/614b6263/codex-reports/report-final.md`에 있다. 이번에는 새 tier2 행동 테스트와 실제 API 삭제 계약을 재검증했고, 실제 모델 첫 메시지는 보내지 않았다.
- status.sh의 `resume_on_disk`는 아직 기본 HOME 경로를 직접 찾는다. 사용자 지정 XDG 저장소에 대한 status 표시는 어댑터의 새 경로 지원과 다를 수 있다. 이는 1차 리뷰에서도 있던 비차단 후속 사항이며 이번 NOT PASS의 추가 사유로 삼지 않았다.
- Linux 실제 실행 결과는 없다. 구문 및 플랫폼 코드 경로를 검토했고 Linux 런타임/서비스 동작 검증은 예정된 후속 작업으로 남는다.
## 증거 파일
`/tmp/review-c4e96018-i02oz_61` (실경로 `/private/tmp/...`):
- head.log — 원본 결과 및 유일한 실패.
- before-fix.log — 이전 HEAD에서 새 회귀 검사 5건 실패.
- real_probe.py, real-probe/result.json — 실제 2.0.32 부모/자식 legacy 정리 확인.
- isolation.log — sandbox 밖 임시 XDG data root 선택 재현.
- isolation-control.log — 임시 격리 목록 보완 후 2건 통과.
- isolation-probe/tests/test_review_xdg_isolation.py — 읽기만 하는 재현 테스트. 이 복사본의 guard에는 대조군 변경이 적용돼 있으므로 원본 재현 시 HEAD의 guard를 사용해야 한다.
- processes-after.json — 시험 후 OpenCode 프로세스 확인.
[VERDICT: NOT PASS]