# Review 4709b38f — Issue #60, opencode 2.x Reviewed only the snapshot `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261018T055915Z-abe16e/1` (`loop-20261018T055915Z-abe16e`). Head `6b4b96fa1875862ef3bed830fe7b04166b1fad58`, tree `9cfb2106081eba96b58cc6b7573031de0acba399`, base `74333f87d0c02888c2b5b281e912d97e74348c01` is an ancestor. Three commits, 13 files, +460/−275. `git diff --check` is clean. Snapshot porcelain is empty and the snapshot has no `.mam`. Control-plane main is `74333f87d0c02888c2b5b281e912d97e74348c01`, porcelain empty. No product file was edited. Host tools: `/usr/bin/python3` 3.9.6, the control-plane venv the same 3.9.6, `/bin/bash` 3.2.57. `bash -n` accepts `create_session.sh`, `resume_session.sh`, `update_yaml_resumed.sh`, `reconcile.sh`, and `status.sh`. `opencode --version` in a temporary home, with `XDG_DATA_HOME`, `XDG_STATE_HOME`, `XDG_CONFIG_HOME`, and `XDG_CACHE_HOME` unset, printed `opencode v2.0.32`. The user's background service stayed up for the whole review. Its pid is 86524, the command is `opencode serve --service`, and the service file mtime stayed 1792246838. The real database mtime stayed 1792247140. Nothing in this review called `session delete`, `service stop`, or an API against that database. After the tests the only `opencode` binary still running was that service. ## What this Mac's database already stores A read-only open of `~/.local/share/opencode/opencode.db` shows a migrated database. `session_v2` and `session` each have 4 rows. Every `session_v2` row has `parent_id` NULL, and every id matches `[A-Za-z0-9_]`. The 2.x tables present are `session_v2`, `session_message`, `session_inbox`, `session_pending`, `event`, and `event_sequence`. The 1.x tables a migration keeps are also present: `session`, `message`, `part`, `todo`, and `session_share`. `todo` and `session_share` are keyed by `session_id`. Column names only were read. No row body was printed. That matches the adapter. `discover` reads `session_v2` where `parent_id IS NULL` (`opencode.py:220`). A child shares the workspace directory and can be newer than the TUI session, so drift C in `reconcile.sh:966-968` uses the same filter. `verify_artifact` still accepts a known child id (`opencode.py:115`). `status.sh` checks `session_v2` in both copies of `resume_on_disk`. ## Purge scope `purge_artifacts` calls `opencode session delete --standalone -- ` only when `session_v2` contains that id (`opencode.py:136-141`). The fake CLI, and the real 2.0.32 contract below, delete that id and its children from the 2.x tables and leave the other session alone. A missing id does not invoke the CLI (`test_purge_does_not_run_opencode_for_a_session_it_does_not_hold`). A missing binary or a non-zero delete writes a `WARN` and leaves the `session_v2` row for `residual_artifacts` (`opencode.py:143-149`, `test_a_failed_delete_is_warned_and_reported`). The SQL that follows deletes `message`, `part`, and `session` (and the plural names) for that same id. It does not delete `todo` or `session_share`. `db_rows_naming` reports any remaining `session_id` or `aggregate_id` rows, so a migrated `todo` stays visible: `test_opencode_residual_reports_event_rows` expects residual `[ (todo.session_id: 1 rows)]` and eight entries for the other session. `session_share.session_id` is on that same report path. The append of `sqlite rows for session` happens when one of those 1.x tables exists (`opencode.py:172-173`), including when the delete matched no row. A fresh 2.x database has none of those tables, and the real contract asserts the purged list is only the session-delete line. ## Spawn, resume, and the real binary `spawn_spec` is `opencode --auto --standalone`. `resume_spec` with a materialized id is `opencode --session --auto --standalone` (`opencode.py:198-204`). The same strings are the `opencode` arms in `create_session.sh`, `resume_session.sh`, and `update_yaml_resumed.sh`. `test_spawn_and_resume_never_pass_agent_and_run_standalone` reads those three scripts and rejects a line that still contains `--agent`. Live skill files and `docs/` have no `opencode db path` and no `--agent build`. `VERSIONS.md`, `bug-report/`, and `.agents/reports/` still describe the 1.x command; the brief leaves those historical notes in place. `create_session.sh` still treats `auth.json` or a provider key as enough, and otherwise accepts `opencode --version`. That check was left as it was, and `--version` exits 0 on this install. `_run` points `HOME` and `XDG_DATA_HOME` at the discovery home before `debug paths` or `session delete` (`opencode.py:80-87`). `_resolve_db` reads the `db` line. The fallback is `/.local/share/opencode/opencode.db`. The opt-in contract `test_real_opencode_2x_contract` ran here, because 2.0.32 is installed. It used a pytest temporary home and tmp, unset the four `XDG_*` variables, and called only `--help`, `api --standalone`, and `session delete --standalone`. `--help` contains `--standalone` and `--session`. Discover returned the parent id. `verify_artifact` on the child was true. Purge of the parent cleared parent and child residuals. A second delete exited 1 with `Session not found`. No model prompt was sent. This review did not start a herdr pane, so it did not drive create, a first TUI message, drift C, stop, and resume as one live chain. The pieces of that chain are the command lines above, drift C's `session_v2` query, and `verify_session_uuid`, which calls the adapter. Stop's graceful `/exit` then kill-session then SIGKILL path is unchanged. `--standalone` is what makes the server a child of that TUI. The id charset on this database has no shell metacharacters, so the unquoted `$UUID` in `resume_session.sh` is safe for the ids stored here. The new code uses `HOME`, `XDG_DATA_HOME`, sqlite, and `subprocess`. Nothing in it is macOS-only. This host is macOS, so Linux was not executed here. ## Tests Pytest used the control-plane venv, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, and `--basetemp` `/tmp/mam4709-4709b38f-pybase`. Inherited `HERDR_*`, `MAM_*`, `MQTT_*`, `SKS_*`, `WORKSPACE_ROOT`, `DELEGATE_JOB_LOGS_DIR`, `HOME_DIR`, `CLAUDE_PROJECT_DIR`, `CLAUDE_CONFIG_DIR`, `CLAUDE_CODE_TMPDIR`, `CODEX_HOME`, and `PYTHONPATH` were dropped. On the snapshot, 44 tests passed in 7.56 s: `tests/test_issue60_opencode_v2.py` (including the real-binary contract), `tests/test_a4_adapter_contract.py`, and `tests/test_issue31_purge_residual.py::test_opencode_residual_reports_event_rows`. No skip and no error. A separate clone whose `discover` query read `session` instead of `session_v2` failed both `test_discover_and_verify_read_session_v2_and_skip_child_sessions` and `test_a_fresh_2x_database_has_no_session_table_and_still_works` (`assert [] == ['ses_main']`), in 1.07 s. A fresh 2.x database has no `session` table, the query raises, and `discover` returns `[]`. The clone was discarded. Snapshot porcelain stayed empty. After the report, snapshot porcelain is still empty, the snapshot still has no `.mam`, and control-plane porcelain is still empty at `74333f87`. The herdr config mtime is still 1784456394. The opencode database mtime is still 1792247140 and service pid 86524 is still `opencode serve --service`. [VERDICT: PASS]