Files
multi-agent-mux/.agents/reports/planner-reviewer-claude-01/report-8a485fa0.md
T

9.9 KiB

Issue #60 리뷰 2차 — opencode 2.x 전용 지원 (job 8a485fa0)

  • 리뷰어: planner-reviewer-claude-01
  • 검증 경로: /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261018T064723Z-79133f/1
  • identity: HEAD cb5f439cce41a2741a6b9974a308a765e50f00ad, tree cfec95403cbde10a6340b9007e1246c83be461cd — 일치, 작업 트리 clean, .mam 없음. base..head 4커밋(a311c23, f0d1c00, 6b4b96f, cb5f439). 판정은 base..head 전체에 대해 내린다.
  • 1차(6b4b96f)에서 확인한 사항(측정 사실↔구현, 실바이너리 session delete 의 자식 삭제·cwd 무관·공유 행 보존, --standalone 수명, 1.x 가정 잔존 없음)은 cb5f439 가 건드리지 않은 부분이라 그대로 유효하다. 이번에는 cb5f439 를 중심으로 봤다.
  • 실제 opencode 는 HOME/TMPDIR 를 scratchpad 로, XDG_* 를 unset 하고 api --standalone·session delete --standalone·debug paths 로만 실행했다. 모델 호출 프롬프트는 보내지 않았다. 매 실험 뒤 남은 opencode 프로세스가 없음을 확인했다(사용자 서비스 pid 86524 제외). 실제 경로는 ls·grep(읽기)으로만 봤다.

결론

1차 지적 5건의 수정은 모두 동작하고, 테스트가 각각을 잡는다. 하지만 N4 수정(XDG_DATA_HOME 따르기)이 테스트 격리를 깨는 새 회귀를 만들었다(B1). 테스트 러너 환경에 XDG_DATA_HOME 이 있으면 sandbox 테스트가 sandbox 밖의 opencode 데이터 디렉터리(Linux 라면 사용자의 실제 DB)를 대상으로 삼고, 2개 테스트가 실패한다. 사용자가 다음에 Linux 호환성 시험을 할 예정이고 브리프가 실제 opencode 데이터 접근을 금지하므로 차단으로 본다. 수정은 fixture 두 줄이다(아래). 설계 변경은 필요 없다.

B1 (차단). XDG_DATA_HOME 이 설정된 러너에서 sandbox 테스트가 sandbox 밖 DB 를 본다

원인: opencode.py:87-94 _data_home 은 ctx.home_dir 이 expanduser("~") 와 같으면 호출자의 XDG_DATA_HOME 을 쓴다. 테스트 fixture mam_sandbox(tests/conftest.py:259)와 home(tests/test_issue31_purge_residual.py:85-96)는 HOME 을 tmp 로 바꾸지만 XDG_DATA_HOME 은 지우지 않는다(conftest.py:307 은 XDG_CONFIG_HOME 만 지움). 그래서 sandbox 의 HOME 이 "자기 홈" 으로 판정되어 러너의 XDG_DATA_HOME/opencode/opencode.db 가 선택된다. 1차 head 는 _run 이 XDG_DATA_HOME=<home>/.local/share 로 강제했으므로 이런 일이 없었다.

재현(검증 경로, env -i … XDG_DATA_HOME=<scratchpad>/j8a/xdg):

pytest tests/test_tier2_component.py tests/test_issue60_opencode_v2.py tests/test_a4_adapter_contract.py tests/test_issue31_purge_residual.py -k opencode
FAILED tests/test_tier2_component.py::test_comp_opencode_create_and_reconcile_and_stop
    assert s_after.get("opencode_session_id_own") == parent   →  None   (drift C 가 sandbox DB 가 아니라 XDG 쪽을 봄)
FAILED tests/test_issue31_purge_residual.py::test_opencode_purge_leaves_no_rows
    '<sandbox>/home/.local/share/opencode/opencode.db (session_v2.id: 1 rows)' in []   (residual 이 XDG 쪽 DB 를 스캔)
2 failed, 21 passed

XDG 위치에 미리 DB 를 둔 대조 실험에서도 residual 스캔과 purge 의 DB 해석이 그 DB 를 가리켰다. 이 실험에서는 sandbox 세션 id 가 무작위라 지워진 행은 없었다. 그래도 Linux 에서 XDG_DATA_HOME 을 둔 사용자가 스위트를 돌리면:

  • reconcile 은 실제 DB 를 읽고,
  • purge 는 실제 DB 에 DELETE … WHERE id=? 트랜잭션과 가짜 session delete 를 실행할 수 있다.
  • #55 가드(tests/_agent_home_guard.py:48)는 $HOME/.local/share/opencode 만 감시하므로 이것을 잡지 못한다.

수정 확인: 사본에서 conftest.py 의 mam_sandbox 에 monkeypatch.delenv("XDG_DATA_HOME", raising=False) 를, issue31 home fixture 에 같은 줄을 넣고 같은 조건으로 실행하자 22 passed, XDG 위치에 DB 접근 흔적이 없었다. 아래 N2 의 --version 로그만 남았다. _agent_home_guard 의 감시 대상에 XDG_DATA_HOME 쪽 opencode 디렉터리를 넣는 것도 권한다.

1차 지적 수정 확인

지적 수정 확인
codex P2: 자식 세션의 1.x 행 잔존, 부모 residual 이 빔 _descendants(opencode.py:195-217)가 삭제 전에 session_v2·1.x session 의 parent_id 를 모든 깊이로 따라가고, _LEGACY_TABLES 8개를 부모+자손 id 로 정리한다. residual 은 자손을 [child session <id>] 로 보고한다 실바이너리 재현: 임시 HOME 에서 api --standalone 으로 P→C→G(3단) + 무관 O 를 만들고, 1.x 형 session(id,parent_id)·message·part·todo·session_share 를 덧붙여 어댑터 purge 를 실행. _descendants(P)==[C,G], purged ['opencode session (with its child sessions): P', 'sqlite rows for session: P and 2 child session(s)'], residual P·C·G 모두 [], O 는 7건 그대로. 순환 방지(cid != uuid and cid not in found), 읽기 전용 URI(quote(abspath)) 확인
codex 주의: 사용자 서비스가 DB 를 잡아 모든 purge 가 exit 4 conversation_in_use(:240-262)가 argv 검사 후 lsof 보유자 중 serve --service 가 아닌 것만 in-use. lsof/ps 예외는 in-use(fail-closed) in-use CLI 시뮬레이션(임시 HOME, 가짜 opencode): 보유자 없음 → rc 0. … serve --service 보유자 → rc 0. … serve --stdio --port 0 보유자 → rc 4. argv 에 id → rc 4. 힌트에 lsof 줄 추가됨
N4: XDG_DATA_HOME 무시 _data_home 제품 동작은 의도대로다(test_the_data_dir_follows…). 다만 B1, N1
agy: 상주 자식 resident_children=('opencode',) stall_watch.background_children(:118-136)는 상주 자식을 건너뛰되 그 자식들은 계속 순회하므로, 서버 아래 도구 프로세스는 여전히 작업으로 잡힌다. 1차에서 실측한 서버 자식 이름(opencode serve --stdio --port 0, argv0 basename opencode)과 맞음
claude N1·N2 실바이너리 테스트가 _bin 을 REAL 로 고정. tier2 가 더 늦은 자식 세션을 시드하고 부모 고정을 단언 PATH 에 opencode 가 없는 env -i 로 test_real_opencode_2x_contract 통과(아래 테스트 결과)

뮤테이션(git archive 사본, -k "opencode and not real_opencode", issue60·issue31·tier2):

변형 실패
자손 제거(ids=[uuid]) 1 (test_purge_takes_the_1x_rows_of_the_child_sessions_too)
residual 의 자손 보고 제거 2
서비스 제외 제거 1 (test_the_background_service_alone…)
XDG 무시 1
resident () 1
reconcile drift C parent_id IS NULL 제거 1 (tier2 — 1차 N2 해소)
_LEGACY_TABLES 에서 todo·session_share 제거 2

테스트 실행 (검증 경로, env -i, PATH 에 opencode 없음, basetemp 는 scratchpad, XDG_DATA_HOME 미설정)

  • 관련 12개 모듈(a4, issue31, issue10_13, issue60, tier1, tier2, issue5_phase0, issue23_24, issue6, c1, issue9, issue30_stall_watch): 473 passed, 1 skipped in 1010 s, rc 0. skip 은 /proc 전용 테스트(macOS). 1차의 N1 이 해소되어 PATH 없이도 실바이너리 계약 테스트가 통과한다. #55 오탐 없음. 실행 뒤 남은 opencode 프로세스 없음.
  • 같은 테스트를 XDG_DATA_HOME=<scratchpad> 로 실행하면 2 failed — B1.

비차단

N1. XDG 를 따르게 되면서 status.sh 가 어긋난다. status.sh:108, :318 은 여전히 {home}/.local/share/opencode 고정이다(reconcile 의 :917 폴백도 같으나 _resolve_db 가 항상 경로를 돌려줘 닿지 않음). Linux 에서 XDG_DATA_HOME 을 둔 사용자라면 reconcile 은 XDG 쪽 DB 에서 id 를 고정하는데 status 는 같은 세션을 MISSING 으로 보인다. 표시만의 문제지만 N4 수정과 같은 범위이므로 status 도 어댑터의 _resolve_db 를 쓰는 편이 낫다. auth_ok·create_session.sh 의 auth.json 검사는 브리프대로 의도적 비변경으로 보았다.

N2. (기존, f0d1c00 부터) test_issue60_opencode_v2.py 를 import 하기만 해도 실제 opencode 가 실제 HOME 으로 실행된다. _real_opencode()(:~300)의 --version 호출이 모듈 import 시점에 러너 환경 그대로 돈다. 그 결과 실제 ~/.local/share/opencode/log/opencode.log 에 args="[\"--version\"]" 줄이 붙는다. 읽기로 확인: 2026-10-18T06:46~06:47Z 에 여러 줄. 내 1차 리뷰 실행도 같은 줄을 남겼음을 밝혀 둔다. 데이터 손상은 아니지만 "실제 ~/.local/share/opencode 쓰기 금지" 에 걸리므로, 버전 확인도 임시 HOME/XDG 로 돌리거나 테스트 본문(임시 HOME 설정 뒤)으로 옮기기를 권한다.

N3. 서비스 제외의 한계(의도된 설계로 보임).

  • 다른 워크스페이스의 MAM opencode 세션(--standalone 서버)이 하나라도 떠 있으면, 그 세션과 무관한 purge 도 여전히 exit 4 다. 안전한 쪽의 오탐이다.
  • 반대로, 사용자가 서비스 모드 TUI 나 웹 UI 로 MAM 세션을 id 없이 열고 있으면 그 사용은 보이지 않는다. MAM 은 --standalone 으로만 띄우므로 실사용 위험은 낮다.
  • Linux 에 lsof 가 없으면 DB 보유자 검사가 통째로 빠진다. base 와 같다.

Python 3.9 / bash / Linux

  • 변경 파일은 Python 3.9 로 파싱된다(/usr/bin/python3 3.9.6, venv 3.9.6). f-string 안의 따옴표 중첩 없음, from urllib.parse import quote 는 표준.
  • ps -o command= -p 는 procps-ng 에서도 지원된다.
  • resident 테스트의 exec -a opencode 는 bash 기능이라 Linux 에서도 동작한다.
  • 셸 스크립트는 cb5f439 에서 바뀌지 않았다.
  • git diff --check 깨끗.

판정

B1 은 fixture 두 줄로 고쳐지고 수정 효과도 확인했다. 그러나 Linux 시험 직전에 사용자 실제 데이터 접근 가능성을 만드는 테스트 격리 회귀이므로 차단으로 둔다. 그 밖의 수정은 모두 PASS 수준이다.

[VERDICT: NOT PASS]