fix(opencode): queries open the database read-only without touching its sidecars (Issue #60, Linux item 5)

A read-write open checkpointed the WAL and deleted -wal/-shm on close; a
plain mode=ro open created missing ones and left them. _ro_connect uses the
sidecars only when both exist (a live writer keeps them) and reads the file
as immutable otherwise. verify_artifact, _has_session, discover,
_descendants and residual_artifacts (db_rows_naming takes a connect) use it;
the purge's own writes are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-10-18 12:31:44 +00:00
1 parent 14d7a46181
commit ac5f1154c7
3 files changed
+103 -8

No files matched your search

+85
View File
@@ -2,11 +2,17 @@
1. The 2.x home screen's placeholder (`Ask anything… "<example>"`, cut
without its closing quote in a narrow box) is not typed input.
5. The adapter's queries neither create nor remove anything beside
opencode's database (a read-write open checkpoints the WAL and deletes
the sidecars on close; a plain mode=ro open creates missing ones).
"""
from __future__ import annotations
import os
import re
import shutil
import sqlite3
import sys
from pathlib import Path
@@ -19,6 +25,7 @@ if str(SKILLS) not in sys.path:
sys.path.insert(0, str(SKILLS))
from lib_py import stall_watch # noqa: E402
from lib_py.agents.base import DiscoveryContext # noqa: E402
from lib_py.agents.input_region import extract_input_region # noqa: E402
from lib_py.agents.registry import get_adapter # noqa: E402
@@ -80,3 +87,81 @@ def test_stall_watch_sees_the_2x_home_box_empty(name, monkeypatch):
def test_other_adapters_have_no_placeholder_pattern():
for agent in ("claude", "agy", "grok", "codex", "hermes"):
assert get_adapter(agent).input_placeholder_pattern is None, agent
# ---------------------------------------------------------------------------
# 5. the queries leave the files beside the database alone
# ---------------------------------------------------------------------------
V2_DDL = ("CREATE TABLE session_v2 (id TEXT PRIMARY KEY, parent_id TEXT, directory TEXT NOT NULL, time_created INTEGER NOT NULL)",
"CREATE TABLE session_message (id TEXT PRIMARY KEY, session_id TEXT)")
QUERIES = {
"verify_artifact": (lambda a, ctx, db: a.verify_artifact("ses_main", ctx), True),
"discover": (lambda a, ctx, db: a.discover(ctx), ["ses_main"]),
"_has_session": (lambda a, ctx, db: a._has_session(db, "ses_main"), True),
"_descendants": (lambda a, ctx, db: a._descendants(db, "ses_main"), ["ses_child"]),
"residual_artifacts": (lambda a, ctx, db: any("session_v2.id: 1 rows" in r
for r in a.residual_artifacts("ses_main", ctx)), True),
}
def _wal_writer(db: Path, ws: Path) -> sqlite3.Connection:
"""An open WAL connection holding the schema and two sessions: until it
closes, the rows live in the -wal only."""
conn = sqlite3.connect(str(db))
conn.execute("PRAGMA journal_mode=wal")
for stmt in V2_DDL:
conn.execute(stmt)
conn.execute("INSERT INTO session_v2 VALUES ('ses_main', NULL, ?, 2000000)", (str(ws),))
conn.execute("INSERT INTO session_v2 VALUES ('ses_child', 'ses_main', ?, 3000000)", (str(ws),))
conn.execute("INSERT INTO session_message VALUES ('msg-1', 'ses_main')")
conn.commit()
return conn
@pytest.fixture
def wal_home(tmp_path, monkeypatch):
"""(adapter, ctx, db, ws, make(state)) - make builds the database in one
of three states: `clean` (closed: no sidecars), `leftover` (both sidecars
left behind by a writer that is gone, the rows only in the -wal),
`writer` (a live writer whose rows are only in the -wal)."""
home, ws = tmp_path / "home", tmp_path / "ws"
ws.mkdir()
db = home / ".local" / "share" / "opencode" / "opencode.db"
db.parent.mkdir(parents=True)
adapter = get_adapter("opencode")
monkeypatch.setattr(adapter, "_bin", lambda ctx: None) # the default path, no subprocess
ctx = DiscoveryContext(workspace=str(ws), agent_name="opencode", home_dir=str(home))
opened = []
def make(state):
if state == "clean":
_wal_writer(db, ws).close()
elif state == "leftover":
src = tmp_path / "src" / "opencode.db"
src.parent.mkdir()
conn = _wal_writer(src, ws)
for suffix in ("", "-wal", "-shm"):
shutil.copyfile(str(src) + suffix, str(db) + suffix)
conn.close()
else:
opened.append(_wal_writer(db, ws))
return sorted(os.listdir(db.parent))
yield adapter, ctx, str(db), ws, make
for conn in opened:
conn.close()
@pytest.mark.parametrize("query", sorted(QUERIES))
@pytest.mark.parametrize("state,listing", [
("clean", ["opencode.db"]),
("leftover", ["opencode.db", "opencode.db-shm", "opencode.db-wal"]),
("writer", ["opencode.db", "opencode.db-shm", "opencode.db-wal"]),
])
def test_a_query_reads_right_and_leaves_the_sidecars_alone(wal_home, state, listing, query):
adapter, ctx, db, ws, make = wal_home
assert make(state) == listing
run, expected = QUERIES[query]
assert run(adapter, ctx, db) == expected
assert sorted(os.listdir(os.path.dirname(db))) == listing