Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
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, treecfec95403cbde10a6340b9007e1246c83be461cd— 일치, 작업 트리 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/python33.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]