release(v4.9.7): opencode 2.x Linux follow-up (Issue #60, PR #62) - 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:
1 parent
6a37c0019b
commit
cffe279eb0
28 files changed
+568
-26
No files matched your search
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261019T015801Z-05463c",
|
||||
"base_sha": "1d7b7dab58a10fe86fd747b7290d47674ca93230",
|
||||
"head_sha": "6a37c0019baa31ee28b32a086e57b4c8239413c4",
|
||||
"tree_sha": "87013e7ef7f5239f1bf6ea38a2cbda214257be3c",
|
||||
"review_commit": "6a37c0019baa31ee28b32a086e57b4c8239413c4",
|
||||
"reviewer": "planner-reviewer-claude-01",
|
||||
"job_id": "1fb2cefa",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-19T02:37:22Z"
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
# PR #62 리뷰 2회차 — Issue #60 Linux 확인 후속 (job 1fb2cefa)
|
||||
|
||||
- 리뷰어: planner-reviewer-claude-01
|
||||
- 검증 경로: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T015801Z-05463c/1`
|
||||
- identity: HEAD `6a37c0019baa31ee28b32a086e57b4c8239413c4`, tree `87013e7ef7f5239f1bf6ea38a2cbda214257be3c` 가 일치한다. 작업 트리는 clean 이고 `.mam` 은 없다.
|
||||
- 범위: base `1d7b7da`..head 9커밋. 판정은 base..head 전체에 대한 것이다. 1회차 이후 바뀐 것은 6a37c00 하나이고, `opencode.py`·`status.sh`·`reconcile.sh`·`test_issue60_linux_fixes.py` 4개 파일만 바뀌었다.
|
||||
- 1회차(@6a4e385)의 확인은 6a37c00 이 건드리지 않은 코드라 그대로 유효하다. 1회차에 확인한 것: 임시 DB 에서 `_ro_connect` 세 갈래, `_DB_PATHS` 키, `auth-ok` CLI, stop 문구.
|
||||
- 환경: macOS, Python 3.9.6, sqlite 3.51.0. 실제 opencode 데이터는 `ls`·`grep -c`·`ps`·`lsof` 로만 봤다.
|
||||
|
||||
## 결론
|
||||
|
||||
1회차의 공통 차단(B1)과 codex·agy 지적이 모두 해결됐고, 새 회귀는 찾지 못했다. base..head 전체를 병합 가능으로 판단한다.
|
||||
|
||||
## 1회차 지적별 확인
|
||||
|
||||
**B1 (clean 상태 5개가 macOS 에서 실패) — 해결.**
|
||||
- `make("clean")` 이 close 뒤 남은 `-wal`·`-shm` 을 지운다(`tests/test_issue60_linux_fixes.py:158-162`). 1회차 사본에서 내가 검증한 수정과 같다.
|
||||
- 검증 경로에서 이 모듈이 모두 통과한다(아래 실행 결과).
|
||||
- 뮤테이션 i1(immutable 분기를 `mode=ro` 로)이 이제 이 Mac 에서 5 failed 로 잡힌다. 1회차에는 fixture 가 먼저 실패해서 이 뮤테이션이 가려졌었다.
|
||||
|
||||
**안내 문구 패턴이 사용자가 친 `Ask anything…` 를 지움(codex) — 해결.**
|
||||
- 패턴이 `Ask anything(?:…|\.\.\.) "[^"]*"?` 가 되어 따옴표 예시가 필수다(`opencode.py:80`).
|
||||
- 두 fixture 에 넣어 직접 확인한 결과:
|
||||
- `Ask anything…`, `Ask anything...` 는 친 그대로 읽힌다.
|
||||
- 홈 화면 원본, 잘린 예시, `Ask anything… "` 는 빈 입력으로 읽힌다.
|
||||
- 회귀 테스트 2개가 추가됐다(`:84-85`). 뮤테이션 p1(예시를 다시 선택 사항으로)은 4 failed 로 잡힌다.
|
||||
|
||||
**auth_ok 가 실행 불가한 `~/.opencode/bin/opencode` 도 인정(codex) — 해결.**
|
||||
- 이제 `isfile and access(X_OK)` 로 본다(`opencode.py:322-323`).
|
||||
- 권한 644 파일과 디렉터리는 모두 False 로 고정됐다(`test_a_home_install_that_cannot_run_is_not_enough`). 뮤테이션 a1(`exists` 로 되돌림)은 1 failed 로 잡힌다.
|
||||
- `shutil.which` 가 이미 실행 가능 여부를 보므로 PATH 쪽은 그대로다.
|
||||
|
||||
**status.sh·reconcile.sh 의 `sqlite3.connect(odb)`(agy) — 해결.**
|
||||
- status 의 opencode 두 블록(`status.sh:115-117`, `:316-318`)과 reconcile drift C(`reconcile.sh:953-955`)가 `_ro_connect` 를 쓴다.
|
||||
- 두 스크립트는 lib_py 를 이미 import 하는 경로에서 돌기 때문에 import 가 된다(status 는 `env_python`/PYTHONPATH, reconcile 은 `env_python`).
|
||||
- status 블록에서 `import sqlite3` 를 지웠지만 영향이 없다. 같은 함수의 다른 블록(hermes `:90`, codex `:132`)은 각자 `import sqlite3` 를 한다.
|
||||
- 행동 확인: git archive 사본에 임시 e2e 를 넣었다. 순서는 이렇다.
|
||||
1. mam_sandbox+mock 에서 create → 자식 세션 추가 → `journal_mode=wal` → 사이드카 삭제(Linux 의 닫힌 상태)
|
||||
2. `reconcile.sh --once --emit-diff` → `status.sh --json` 과 plain status
|
||||
- 결과(head): rc 는 모두 0. 부모 id 로 고정됐고 `resume_state == "yes"` 이다. DB 디렉터리는 실행 전후 모두 `['opencode.db']` 였다.
|
||||
- 대조(두 스크립트를 `sqlite3.connect(odb)` 로 되돌린 사본): reconcile 뒤 `opencode.db-shm`·`opencode.db-wal` 이 생겨 같은 테스트가 실패했다.
|
||||
- 참고: mock opencode 가 만드는 DB 는 WAL 모드가 아니다. 그래서 WAL 로 바꾸지 않으면 이 차이가 드러나지 않는다.
|
||||
|
||||
## 테스트 실행 (검증 경로)
|
||||
|
||||
- 조건: `env -i`, 임시 HOME, `XDG_DATA_HOME=<scratchpad>`, basetemp 는 scratchpad, `-p no:cacheprovider`, `PYTHONDONTWRITEBYTECODE=1`.
|
||||
- 대상 12개 모듈: issue60_linux_fixes, issue60_opencode_v2, a4, tier1, tier2, issue31, issue55, issue57, issue21_worktree_prereqs, issue50_dispatch, issue30_stall_watch, c1_tui_readiness.
|
||||
- 결과: **463 passed, 2 skipped, 실패 0**, 16분.
|
||||
- skip 1: `/proc` 전용 테스트.
|
||||
- skip 2: PATH 에 opencode 가 없어 건너뛴 실바이너리 계약 테스트(1회차·이전 이슈에서 따로 확인함).
|
||||
- 부작용 확인:
|
||||
- scratchpad XDG 디렉터리는 실행 뒤에도 비어 있었다.
|
||||
- 실제 `~/.local/share/opencode` 의 목록은 전후가 같았다.
|
||||
- 실제 log 의 `debug` 줄 수는 2 → 2 였다.
|
||||
- 남은 opencode 프로세스는 사용자 서비스 하나뿐이다(pid 23630, 시작 08:13, 이 잡 이전).
|
||||
- #55 가드 오탐은 없었다.
|
||||
- 린트:
|
||||
- `git diff --check 1d7b7da..HEAD` 는 깨끗하다.
|
||||
- 바뀐 Python 은 3.9 로 파싱된다.
|
||||
- `status.sh`·`reconcile.sh` 는 `/bin/bash -n`(3.2)을 통과한다.
|
||||
|
||||
## 비차단
|
||||
|
||||
- **N1.** 새 `test_status_and_reconcile_open_the_database_read_only` 는 소스 문자열만 센다. `sqlite3.connect(odb, timeout=…)` 같은 변형은 지나간다. 위의 임시 e2e(WAL DB, 사이드카 없음 → reconcile·status → 목록 불변)를 tier2 opencode e2e 에 붙이면 행동으로 고정할 수 있다.
|
||||
- **N2.** 1회차 비차단은 의도된 완화로 그대로 남아 있다(브리프에도 기록됨):
|
||||
- immutable 경쟁 창(ms 수준)
|
||||
- `_DB_PATHS` 의 프로세스 수명 캐시
|
||||
- create 사전 검사의 완화
|
||||
- **N3.** 아주 좁은 화면에서 예시의 여는 따옴표까지 잘리면 `Ask anything…` 만 남는다. 이때는 친 초안으로 읽힌다(안전한 쪽). 실제 캡처로는 확인하지 않았다.
|
||||
|
||||
[VERDICT: PASS]
|
||||
@@ -0,0 +1,93 @@
|
||||
# PR #62 리뷰 — Issue #60 Linux 확인 후속 (job dd5c8c62)
|
||||
|
||||
- 리뷰어: planner-reviewer-claude-01
|
||||
- 검증 경로: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T000805Z-975c4c/1`
|
||||
- identity: HEAD `6a4e385a78c8a561d5cf3644336de6a352ea9271`, tree `afa2c55925f93820337b69699c670c0b0ca90980` 일치. 작업 트리 clean, `.mam` 없음. base `1d7b7da`..head 8커밋. 판정은 base..head 전체에 대한 것이다.
|
||||
- 환경: macOS, `/usr/bin/python3` 3.9.6 와 `.venv` python 3.9.6, 둘 다 sqlite 3.51.0. 실제 opencode 데이터는 `ls`·`grep -c` 로만 봤다. 실제 DB 는 sqlite 로 열지 않았다.
|
||||
|
||||
## 결론
|
||||
|
||||
제품 코드에서는 차단 결함을 찾지 못했다. 다만 새 테스트 5개가 이 Mac 에서 항상 실패한다(B1). 이 리뷰의 중점이 macOS 정합성이고, 오케스트레이터의 전체 스위트도 이 Mac 에서 돈다. 그래서 NOT PASS 로 둔다. 고칠 곳은 fixture 몇 줄이고 재설계는 필요 없다.
|
||||
|
||||
## 차단
|
||||
|
||||
**B1. `test_a_query_reads_right_and_leaves_the_sidecars_alone[clean-*]` 5개가 macOS 에서 실패한다.**
|
||||
- 위치: `tests/test_issue60_linux_fixes.py:153-154`(`make("clean")`)과 `:175-181`.
|
||||
- 원인: macOS 시스템 SQLite 는 persistent WAL 로 동작해서, 마지막 연결을 닫아도 `-wal`·`-shm` 을 지우지 않는다. 임시 DB 에서 쓰기 연결을 닫은 뒤의 목록이 `['o.db', 'o.db-shm', 'o.db-wal']` 였다. 그래서 `_wal_writer(db, ws).close()` 뒤의 목록이 기대값 `["opencode.db"]` 와 다르고, 첫 assert(`make(state) == listing`)에서 실패한다. 제품 코드까지 가지도 않는다.
|
||||
- 재현: 검증 경로에서 `env -i …` 로 `tests/test_issue60_linux_fixes.py` 를 단독 실행하면 5 failed, 40 passed. 관련 18개 모듈 실행(아래)에서도 같은 5개만 실패했다.
|
||||
- 제품 코드는 맞다. 이 테스트의 `clean` 상태는 Linux 에서 닫힌 DB 의 모습(사이드카 없음)이다. git archive 사본에서 이 상태를 만들도록 close 뒤에 `-wal`·`-shm` 을 지워 보았다. 그러자 해당 서브셋 33개가 모두 통과했고, 아래 뮤테이션 6개도 모두 잡혔다.
|
||||
- 권고: `clean` 분기에서 close 뒤 남은 `-wal`·`-shm` 을 지운다. close 가 이미 checkpoint 하므로 행은 본 파일에 있다. 또는 Mac 의 실제 상태인 "쓰는 쪽 없이 사이드카 둘 다"는 이미 `leftover` 가 다루므로, 그 사실을 주석으로 남긴다.
|
||||
- 주의: 지금 상태의 Mac 에서는 `clean` 이 첫 assert 에서 멈춘다. 그래서 immutable 분기를 없애는 뮤테이션(M1)이 이 Mac 에서는 드러나지 않는다.
|
||||
|
||||
## 중점 항목 확인
|
||||
|
||||
**1. `_ro_connect` 세 갈래 (`opencode.py:13-36`).** 검증 경로 코드를 3.9 로 import 해 임시 DB 에서 확인했다. 사이드카를 직접 지워 Linux 와 같은 상태를 만들었다.
|
||||
|
||||
| 상태 | 결과 |
|
||||
|---|---|
|
||||
| 사이드카 없음 | 같은 상태에서 base 의 평범한 `mode=ro` 는 `unable to open database file` 로 실패한다. 그래서 base 의 `_descendants`·`db_rows_naming` 은 Mac 에서도 빈 결과였다. `_ro_connect`(immutable)는 행을 읽고 아무 파일도 만들지 않는다. |
|
||||
| writer 가 살아 있음(둘 다 있음) | WAL 에만 있는 행까지 읽는다. 목록은 그대로다. |
|
||||
| `-shm` 없는 `-wal` | 커밋된 행을 읽는다. 원본 두 파일의 mtime 은 그대로였고 `-shm` 도 생기지 않았다. 같은 상태에서 평범한 `mode=ro` 는 `-shm` 을 만들었다. 디렉터리가 읽기 전용(0555)이어도 읽힌다. |
|
||||
| DB 없음 | `OperationalError`. 파일은 생기지 않는다. base 의 `sqlite3.connect(db)` 는 빈 DB 를 만들었으므로 개선이다. |
|
||||
|
||||
- 실제 Mac 의 상태: 실제 디렉터리에는 서비스가 떠 있어 `opencode.db`·`-shm`·`-wal` 이 모두 있다(`ls`). persistent WAL 이 기본인 Mac 에서는 "둘 다 있음 → mode=ro" 갈래가 대부분일 것이다. 다만 bun 이 시스템 SQLite 를 쓰는지는 확인하지 않았다.
|
||||
|
||||
**2. `_DB_PATHS` 캐시 (`opencode.py:39-40, 139-161`).**
|
||||
- 키는 `(bin, ctx.home_dir, _data_home(ctx))` 다. `_run` 이 넘기는 HOME·XDG_DATA_HOME 과 같은 값이라, 다른 HOME 이나 XDG 에 다른 경로를 돌려주지 않는다.
|
||||
- 실패한 `debug paths` 는 저장되지 않는다. 바이너리가 없으면(`bin_path` None) 캐시를 쓰지 않는다.
|
||||
- 테스트 간 누출: 이 모듈은 autouse fixture 로 캐시를 비운다. 다른 모듈은 tmp_path 별 HOME 이라 키가 겹치지 않는다. 18개 모듈을 한 프로세스에서 실행했을 때 B1 외의 실패는 없었다.
|
||||
|
||||
**3. auth_ok (`opencode.py:317-321`, `__main__.py:120-127`, `create_session.sh:145-151`).**
|
||||
- 다른 에이전트의 분기와 adapter 는 바뀌지 않았다. `auth-ok` 를 부르는 곳은 opencode 분기뿐이다.
|
||||
- `python -m lib_py.agents auth-ok opencode` 를 `/usr/bin/python3` 3.9 로 실행했다. PATH 에 opencode 가 없으면 rc 1, `~/.opencode/bin` 이 있으면 rc 0, 인자가 없으면 rc 1(`unknown agent ''`).
|
||||
- PYTHONPATH 는 lib.sh 가 export 한다(`lib.sh:25`). 같은 파일의 `facts`·`spawn-spec` 호출과 같은 방식이다.
|
||||
- create 의 사전 검사로 충분하다. 바로 위 `command -v "$AGENT"`(`:117`)가 이미 PATH 의 바이너리를 요구한다.
|
||||
|
||||
**4. `input_placeholder_pattern` (`opencode.py:75-79`, `input_region.py:83-85`).**
|
||||
- 홈 화면 fixture 두 개(140열, 잘린 예시가 있는 60열)는 모두 빈 입력으로 읽힌다. 1.x 캡처도 그대로 빈 입력이다. 다른 adapter 는 None 이다(테스트로 고정).
|
||||
- fullmatch 라 `Ask anything… "x" and fix it` 처럼 뒤에 글이 붙은 입력은 그대로 읽힌다.
|
||||
|
||||
**5. stop 문구 (`stop_session.sh:242`).** 메시지만 바뀌었고 동작은 같다. 테스트 2개(exit 됨 / 살아 있어 kill)가 통과했다.
|
||||
|
||||
**6. Python 3.9 와 셸.**
|
||||
- 바뀐 Python 5개 파일이 3.9 에서 파싱되고, 제품 모듈은 3.9 로 import 해 실행했다. 새 테스트는 `from __future__ import annotations` 를 쓴다.
|
||||
- `create_session.sh`·`stop_session.sh` 가 `/bin/bash -n`(3.2)을 통과한다. `git diff --check 1d7b7da..HEAD` 는 깨끗하다.
|
||||
|
||||
## 테스트 실행 (검증 경로)
|
||||
|
||||
조건: `env -i`, 임시 HOME, `XDG_DATA_HOME=<scratchpad>`, basetemp 는 scratchpad, `-p no:cacheprovider`, `PYTHONDONTWRITEBYTECODE=1`.
|
||||
|
||||
대상 18개 모듈: issue60_linux_fixes, issue60_opencode_v2, a4, issue50_dispatch, issue30_stall_watch(+_stop), issue31, tier1, tier2, c1_tui_readiness, issue5_phase0/phase2b, issue24, issue6, uuid_target, issue55, issue57, issue21_worktree_prereqs.
|
||||
|
||||
- 결과: **5 failed(B1 의 5개뿐), 623 passed, 2 skipped**, 29분.
|
||||
- skip 2개: `/proc` 전용 테스트와, PATH 에 opencode 가 없어 건너뛴 실바이너리 계약 테스트.
|
||||
- 부작용 확인:
|
||||
- scratchpad XDG 디렉터리는 실행 뒤에도 비어 있었다.
|
||||
- 실제 `~/.local/share/opencode` 목록은 실행 전후가 같았다.
|
||||
- 실제 log 의 `debug` 줄 수는 2 → 2.
|
||||
- 남은 opencode 프로세스는 없었다(사용자 서비스 제외).
|
||||
- #55 가드 오탐도 없었다.
|
||||
|
||||
뮤테이션: git archive 사본에 B1 fixture 수정을 넣고 진행했다. 대상은 issue60_linux_fixes 와 a4 이며, 실제 프로세스를 띄우는 stop·create 2개는 뺐다. 모두 잡혔다.
|
||||
|
||||
| 뮤테이션 | 결과 |
|
||||
|---|---|
|
||||
| M1 immutable 분기를 `mode=ro` 로 | 5 failed |
|
||||
| M2 `wal and shm` 를 `wal` 로 | 5 failed |
|
||||
| M3 캐시 사용 끔 | 1 failed |
|
||||
| M4 잘린 예시 허용(`"?`) 제거 | 2 failed |
|
||||
| M5 fullmatch 를 match 로 | 4 failed |
|
||||
| M6 auth_ok 를 항상 True 로 | 3 failed |
|
||||
|
||||
## 비차단
|
||||
|
||||
- **N1. immutable 경쟁.** `-wal` 없음을 판단한 뒤 연결이 쓰이는 동안 writer 가 생기면 결과가 틀릴 수 있다. 임시 DB 에서 재현했다.
|
||||
- (a) 판단 직후 writer 가 한 행을 커밋하면, immutable 연결은 1/2 만 본다.
|
||||
- (b) 열린 immutable 커서 아래에서 writer 가 지우고 다시 쓰고 checkpoint 하면, 예외 없이 옛 행과 새 행이 섞인 20119행이 나왔다(옛 20000행, 새 30000행).
|
||||
- 쿼리가 짧아(LIMIT 20, 단건 조회) 실제 창은 ms 수준이다. persistent WAL 인 Mac 에서는 이 갈래 자체가 드물다.
|
||||
- 원하면 이렇게 줄일 수 있다: 쿼리 뒤 `-wal` 이 여전히 없고 본 파일의 mtime·size 가 그대로인지 확인하고, 아니면 `mode=ro` 로 한 번 다시 읽는다.
|
||||
- **N2. 캐시는 프로세스 수명 동안 유지된다.** 같은 경로에서 바이너리를 업그레이드한 경우, 오래 도는 `reconcile --subscribe` 는 예전 `debug paths` 결과를 계속 쓴다. 경로 규칙이 바뀌는 업그레이드에서만 문제가 된다.
|
||||
- **N3. 사전 검사가 약해졌다.** create 의 opencode 검사는 사실상 `command -v` 와 같아졌다. 깨진 바이너리도 통과하고, 실패는 이후 준비 대기 단계에서 드러난다. 2.x 에 로그인이 없다는 전제와 맞는 완화로 본다.
|
||||
- **N4. 안내 문구와 같은 모양의 초안.** 사용자가 정확히 그 모양으로 친 입력(`Ask anything...`, `Ask anything… "fix the tests"`)은 빈 입력으로 읽힌다. 확인한 결과 둘 다 `''` 였다. 실사용에서 생길 가능성은 매우 낮다.
|
||||
|
||||
[VERDICT: NOT PASS]
|
||||
Reference in new issue
Block a user