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,93 @@
|
||||
# PR #62 Code Review Report — Issue #60 Linux Follow-up (`1d7b7da..6a4e385`)
|
||||
|
||||
- **Job ID**: `ba63f0b5`
|
||||
- **Reviewer**: `agy` (`herdr:reviewer-agy-01`)
|
||||
- **Branch**: `fix/issue-60-linux`
|
||||
- **Base .. Head**: `1d7b7dab58a10fe86fd747b7290d47674ca93230` .. `6a4e385a78c8a561d5cf3644336de6a352ea9271` (8 commits)
|
||||
- **Verification Worktree**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T000805Z-975c4c/1`
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive Summary
|
||||
|
||||
All production code changes in PR #62 (`_ro_connect`, `_DB_PATHS` cache, `input_placeholder_pattern`, `auth_ok` / `auth-ok`, `stop_session.sh` log text, and `test_u1e`) are well-designed and behave as intended on macOS (Python 3.9.6, Apple SQLite 3.51.0, opencode 2.0.32).
|
||||
|
||||
However, **5 test cases in `tests/test_issue60_linux_fixes.py` fail on macOS** (`test_a_query_reads_right_and_leaves_the_sidecars_alone[clean-listing0-*]`) due to a macOS vs. Linux SQLite difference in `wal_home`'s `make("clean")` fixture helper:
|
||||
- On Linux (upstream SQLite default `SQLITE_FCNTL_PERSIST_WAL = 0`), closing the last connection to a WAL database (`_wal_writer(db, ws).close()`) checkpoints the WAL into `opencode.db` and unlinks `opencode.db-wal` and `opencode.db-shm`.
|
||||
- On macOS (`/usr/bin/python3` linked against Apple's `/usr/lib/libsqlite3.dylib`, which sets `SQLITE_FCNTL_PERSIST_WAL = 1` by default), `_wal_writer(db, ws).close()` checkpoints the WAL into `opencode.db` and truncates `opencode.db-wal` to 0 bytes, **but leaves `opencode.db-wal` and `opencode.db-shm` on disk**.
|
||||
- Consequently, `assert make(state) == listing` at `tests/test_issue60_linux_fixes.py:181` fails with `AssertionError: assert ['opencode.db', 'opencode.db-shm', 'opencode.db-wal'] == ['opencode.db']` before `_ro_connect` is even called.
|
||||
|
||||
Unlinking `opencode.db-wal` and `opencode.db-shm` after `_wal_writer(db, ws).close()` in `make("clean")` makes all 20 `(query × state)` cases pass on macOS as well.
|
||||
|
||||
---
|
||||
|
||||
## 2. Findings
|
||||
|
||||
### `[P1]` Blocking — `wal_home` fixture `make("clean")` fails on macOS due to Apple SQLite `SQLITE_FCNTL_PERSIST_WAL=1`
|
||||
|
||||
- **Location**: `tests/test_issue60_linux_fixes.py:157-159, 181`
|
||||
- **Symptom**:
|
||||
Running `pytest tests/test_issue60_linux_fixes.py` on macOS (`/usr/bin/python3` 3.9.6, `sqlite3.sqlite_version == '3.51.0'`) fails 5 tests:
|
||||
- `FAILED tests/test_issue60_linux_fixes.py::test_a_query_reads_right_and_leaves_the_sidecars_alone[clean-listing0-_descendants]`
|
||||
- `FAILED tests/test_issue60_linux_fixes.py::test_a_query_reads_right_and_leaves_the_sidecars_alone[clean-listing0-_has_session]`
|
||||
- `FAILED tests/test_issue60_linux_fixes.py::test_a_query_reads_right_and_leaves_the_sidecars_alone[clean-listing0-discover]`
|
||||
- `FAILED tests/test_issue60_linux_fixes.py::test_a_query_reads_right_and_leaves_the_sidecars_alone[clean-listing0-residual_artifacts]`
|
||||
- `FAILED tests/test_issue60_linux_fixes.py::test_a_query_reads_right_and_leaves_the_sidecars_alone[clean-listing0-verify_artifact]`
|
||||
- **Root Cause**:
|
||||
Apple's system `libsqlite3.dylib` enables `SQLITE_FCNTL_PERSIST_WAL` by default. When `_wal_writer(db, ws).close()` runs on macOS, SQLite checkpoints the committed WAL frames into `opencode.db` (`8192` bytes) and truncates `opencode.db-wal` to `0` bytes, but keeps `opencode.db-wal` and `opencode.db-shm` on disk instead of unlinking them.
|
||||
- **Verification & Fix**:
|
||||
In `make(state)` (`tests/test_issue60_linux_fixes.py:157-159`), removing any remaining sidecars after `_wal_writer(db, ws).close()` checkpoints the DB:
|
||||
```python
|
||||
def make(state):
|
||||
if state == "clean":
|
||||
_wal_writer(db, ws).close()
|
||||
for suffix in ("-wal", "-shm"):
|
||||
if os.path.exists(str(db) + suffix):
|
||||
os.unlink(str(db) + suffix)
|
||||
```
|
||||
leaves `opencode.db` with its WAL header and checkpointed rows and no sidecar files (`["opencode.db"]`). Verified on macOS that all 20 `(query × state)` combinations pass with this change, and that `_ro_connect(db)`'s `mode=ro&immutable=1` branch is crucial on macOS (where a plain `mode=ro` open on a sidecar-free WAL-mode DB raises `sqlite3.OperationalError: unable to open database file`, and a plain read-write `sqlite3.connect(db)` creates and leaves `-wal` and `-shm`).
|
||||
|
||||
---
|
||||
|
||||
### `[P2]` Non-blocking — `status.sh` and `reconcile.sh` still open `opencode.db` with read-write `sqlite3.connect(odb)`
|
||||
|
||||
- **Locations**:
|
||||
- `.agents/skills/multi-agent-mux-status/scripts/status.sh:116, 316`
|
||||
- `.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh:953`
|
||||
- **Observation**:
|
||||
While `OpenCodeAgentAdapter` methods (`verify_artifact`, `discover`, `_has_session`, `_descendants`, `residual_artifacts`) now use `_ro_connect(db)`, `status.sh` (both `--json` and text mode) and `reconcile.sh` (drift C, which `status.sh` also runs via `--dry-run`) still call `sqlite3.connect(odb)` directly:
|
||||
- On macOS, calling `sqlite3.connect(odb)` on a sidecar-free WAL-mode `opencode.db` creates `opencode.db-wal` and `opencode.db-shm` and leaves them on disk after `.close()`.
|
||||
- On Linux, calling `sqlite3.connect(odb)` on a `leftover` WAL database checkpoints the WAL and deletes `opencode.db-wal` and `opencode.db-shm` on `.close()`.
|
||||
- **Suggestion**:
|
||||
In `status.sh:108-120, 308-320`, replace the raw `sqlite3.connect(odb)` block with `get_adapter('opencode')._has_session(odb, u)` (which already uses `_ro_connect`), and in `reconcile.sh:953`, use `_ro_connect(odb)` from `lib_py.agents.adapters.opencode`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Detailed Review by Checklist Item
|
||||
|
||||
1. **`_ro_connect` on macOS (`.agents/skills/lib_py/agents/adapters/opencode.py:13-36`)**:
|
||||
- **Both `-wal` and `-shm` present (`leftover` / `writer`)**: `sqlite3.connect(f"file:{quote(path)}?mode=ro", uri=True)` reads uncommitted-to-main-file (WAL-committed) rows through the existing WAL index without creating or removing sidecars.
|
||||
- **No `-wal` (`clean`)**: `sqlite3.connect(f"file:{quote(path)}?mode=ro&immutable=1", uri=True)` reads `opencode.db` directly without creating `-wal`/`-shm` (and avoids `sqlite3.OperationalError: unable to open database file` that plain `mode=ro` raises on macOS when opening a WAL-header DB without sidecars). Because every query opens, fetches, and immediately closes `conn`, any race with a writer starting right after `os.path.exists(path + "-wal")` only sees the pre-WAL snapshot for that single short read and never holds an immutable handle across writes.
|
||||
- **`-wal` without `-shm` (`walonly`)**: Copies `opencode.db` and `opencode.db-wal` into a `tempfile.TemporaryDirectory(prefix="mam-opencode-")`, opens the private copy so SQLite can rebuild `-shm` and recover the committed WAL frames, backs it up to `:memory:`, and cleans up the temp dir. Verified on macOS that committed rows in `-wal` are returned and the source directory remains `['opencode.db', 'opencode.db-wal']`.
|
||||
2. **`_DB_PATHS` process-local cache (`opencode.py:40, 144-160`)**:
|
||||
- Keyed by `(bin_path, ctx.home_dir, self._data_home(ctx))` (or `(bin_path, HOME, XDG_DATA_HOME)` when `ctx.home_dir` is unset).
|
||||
- Different `HOME`, `XDG_DATA_HOME`, or binary paths produce distinct cache keys; failed `debug paths` runs (`returncode != 0` or no `db` line) are not cached.
|
||||
3. **`auth_ok` & `create_session.sh` (`opencode.py:317-321`, `__main__.py:120-127`, `create_session.sh:146-151`)**:
|
||||
- Checks binary existence on `PATH` or `<home>/.opencode/bin/opencode` without spawning `opencode --version` (preventing log writes to the real opencode data dir). Other agents' branches in `create_session.sh` are untouched.
|
||||
4. **`input_placeholder_pattern` (`opencode.py:75-79`, `base.py:212-217`, `input_region.py:80-81`)**:
|
||||
- `re.fullmatch(r'Ask anything(?:…|\.\.\.)(?: "[^"]*"?)?', head.strip())` strips both full and narrow-truncated 2.x home screen placeholders while preserving user input starting with `Ask anything…` (such as `'Ask anything… "x" and fix it'`). Other adapters return `None`.
|
||||
5. **Python 3.9 & macOS compatibility**:
|
||||
- All syntax, type annotations, and shell scripts are compatible with Python 3.9.6 and macOS bash 3.2 (`set -euo pipefail`). `git diff --check` is clean.
|
||||
|
||||
---
|
||||
|
||||
## 4. Test Execution Summary
|
||||
|
||||
Executed in `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T000805Z-975c4c/1` with isolated `HOME`, external `--basetemp`, `PYTHONDONTWRITEBYTECODE=1`, and `-p no:cacheprovider`:
|
||||
- `tests/test_issue60_opencode_v2.py`: 15 passed
|
||||
- `tests/test_a4_adapter_contract.py`: 33 passed
|
||||
- `tests/test_issue50_dispatch.py`: 49 passed
|
||||
- `tests/test_issue55_agent_homes.py`: 11 passed
|
||||
- `tests/test_issue60_linux_fixes.py`: 40 passed, **5 failed** (`test_a_query_reads_right_and_leaves_the_sidecars_alone[clean-listing0-*]`)
|
||||
|
||||
[VERDICT: NOT PASS]
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261019T015801Z-05463c",
|
||||
"base_sha": "1d7b7dab58a10fe86fd747b7290d47674ca93230",
|
||||
"head_sha": "6a37c0019baa31ee28b32a086e57b4c8239413c4",
|
||||
"tree_sha": "87013e7ef7f5239f1bf6ea38a2cbda214257be3c",
|
||||
"review_commit": "6a37c0019baa31ee28b32a086e57b4c8239413c4",
|
||||
"reviewer": "reviewer-agy-01",
|
||||
"job_id": "fe8fd3b9",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-19T02:37:22Z"
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
# PR #62 Code Review Report — Round 2 (`1d7b7da..6a37c00`)
|
||||
|
||||
- **Job ID**: `fe8fd3b9`
|
||||
- **Reviewer**: `agy` (`herdr:reviewer-agy-01`)
|
||||
- **Branch**: `fix/issue-60-linux`
|
||||
- **Base .. Head**: `1d7b7dab58a10fe86fd747b7290d47674ca93230` .. `6a37c0019baa31ee28b32a086e57b4c8239413c4` (9 commits; Round 2 delta: `6a37c00`)
|
||||
- **Verification Worktree**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T015801Z-05463c/1`
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive Summary
|
||||
|
||||
Commit `6a37c00` resolves all blocking and non-blocking findings from Round 1 (`6a4e385`):
|
||||
|
||||
1. **macOS SQLite persistent WAL in `wal_home` fixture (`tests/test_issue60_linux_fixes.py:155-162`)**:
|
||||
- After `_wal_writer(db, ws).close()` checkpoints the committed WAL rows into `opencode.db`, `make("clean")` removes `opencode.db-wal` and `opencode.db-shm` if present (which Apple's system `libsqlite3.dylib` keeps on disk due to `SQLITE_FCNTL_PERSIST_WAL=1`).
|
||||
- All 20 `(query × state)` combinations in `test_a_query_reads_right_and_leaves_the_sidecars_alone` now pass on macOS.
|
||||
2. **Quoted example required in `input_placeholder_pattern` (`.agents/skills/lib_py/agents/adapters/opencode.py:76-79`)**:
|
||||
- Pattern updated to `r'Ask anything(?:…|\.\.\.) "[^"]*"?'` so a bare `Ask anything…` or `Ask anything...` typed by a user is preserved as draft input (`test_typed_text_is_read_as_typed`), while the 2.x home screen placeholder (both full and narrow-truncated) is stripped.
|
||||
3. **Executable check for `~/.opencode/bin/opencode` in `auth_ok()` (`.agents/skills/lib_py/agents/adapters/opencode.py:321-323`)**:
|
||||
- `auth_ok()` now checks `os.path.isfile(local) and os.access(local, os.X_OK)` for `<home>/.opencode/bin/opencode` (`test_a_home_install_that_cannot_run_is_not_enough`).
|
||||
4. **Read-only `_ro_connect` in `status.sh` and `reconcile.sh` (`.agents/skills/multi-agent-mux-status/scripts/status.sh:115-117, 316-318`, `.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh:953-955`)**:
|
||||
- Both scripts now open `opencode.db` via `_ro_connect(odb)` instead of raw `sqlite3.connect(odb)`, pinned by `test_status_and_reconcile_open_the_database_read_only`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Verification of Cumulative Changes (`1d7b7da..6a37c00`)
|
||||
|
||||
| Area | Verification Result |
|
||||
| :--- | :--- |
|
||||
| **`_ro_connect` (`opencode.py:13-36`)** | Verified all 3 branches (`wal and shm` -> `mode=ro`, `not wal` -> `mode=ro&immutable=1`, `wal and not shm` -> temp copy + `.backup(:memory:)`) across all 5 adapter queries and 4 DB states (`clean`, `leftover`, `walonly`, `writer`) on macOS Python 3.9.6 / Apple SQLite 3.51.0. Sidecars are neither created nor deleted. |
|
||||
| **`_DB_PATHS` cache (`opencode.py:40, 145-166`)** | Process-local cache keyed by `(bin_path, HOME, XDG_DATA_HOME)` caches successful `debug paths` lookups and does not cache failures. |
|
||||
| **`input_placeholder_pattern` (`opencode.py:75-79`, `base.py:212-217`, `input_region.py:80-81`)** | Matches `Ask anything(?:…|\.\.\.) "[^"]*"?` via `re.fullmatch` on the first non-blank line of the input box only; preserves user drafts including bare `Ask anything…` / `Ask anything...`. |
|
||||
| **`auth_ok` & `auth-ok` CLI (`opencode.py:319-323`, `__main__.py:120-127`, `create_session.sh:146-151`)** | Verifies `shutil.which("opencode")` or executable `<home>/.opencode/bin/opencode` without running `opencode --version`. |
|
||||
| **`status.sh`, `reconcile.sh`, `stop_session.sh`** | `status.sh` and `reconcile.sh` use `_ro_connect(odb)`; `stop_session.sh` logs unconfirmed `/exit` delivery accurately without altering exit codes or kill-chain behavior. |
|
||||
|
||||
---
|
||||
|
||||
## 3. Test Execution Summary
|
||||
|
||||
Executed in `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T015801Z-05463c/1` with isolated `HOME`, external `--basetemp`, `PYTHONDONTWRITEBYTECODE=1`, and `-p no:cacheprovider`:
|
||||
|
||||
- `tests/test_issue60_linux_fixes.py`: 52 passed
|
||||
- `tests/test_issue60_opencode_v2.py`: 15 passed
|
||||
- `tests/test_a4_adapter_contract.py`: 33 passed
|
||||
- `tests/test_issue50_dispatch.py`: 49 passed
|
||||
- `tests/test_issue55_agent_homes.py`: 11 passed
|
||||
- `tests/test_tier2_component.py`: 42 passed
|
||||
- **Total**: `202 passed in 377.34s`
|
||||
- **Post-check**: Verification worktree is clean (`git status -s` empty, `git diff --check` clean, no `.mam` directory), and no stray `opencode` test processes remain.
|
||||
|
||||
---
|
||||
|
||||
## 4. Findings
|
||||
|
||||
No blocking or non-blocking issues remaining. PR #62 (`fix/issue-60-linux`, `1d7b7da..6a37c00`) is ready to merge.
|
||||
|
||||
[VERDICT: PASS]
|
||||
Reference in new issue
Block a user