# Review Report: Issue #60 (`fix/issue-60-opencode-v2`) - **Job ID**: `1abe0a90` - **Reviewer**: `herdr:reviewer-agy-01` (`agy`) - **Commit Range**: `74333f87d0c02888c2b5b281e912d97e74348c01` .. `6b4b96fa1875862ef3bed830fe7b04166b1fad58` (3 commits across 13 files) - **Verification Worktree**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261018T055915Z-abe16e/1` - **Host / Runtime**: macOS `arm64-apple-darwin25` (Darwin 25.3.0), `/usr/bin/python3` 3.9.6 & `.venv/bin/python` 3.9.6, `/bin/bash` 3.2.57, `opencode` v2.0.32 (`/Users/godopu16/.opencode/bin/opencode`) --- ## 1. Review Findings by Focus Area ### 1.1 OpenCode 2.x Schema, CLI Contract, and Purge Safety - **`_resolve_db` (`opencode debug paths`)**: - `.agents/skills/lib_py/agents/adapters/opencode.py:88-107` invokes `self._run(ctx, ["debug", "paths"], timeout=5)` with `HOME`, `HOME_DIR`, and `XDG_DATA_HOME=/.local/share`, parses the `db` line, and falls back to `f"{ctx.home_dir}/.local/share/opencode/opencode.db"`. - Verified against real `opencode v2.0.32` in an isolated temporary `HOME`: `debug paths` returns `/.local/share/opencode/opencode.db` without creating the database file. - **`verify_artifact` and `discover` (`session_v2` & `parent_id IS NULL`)**: - `verify_artifact` (`.agents/skills/lib_py/agents/adapters/opencode.py:113-147`) queries `SELECT directory, time_created FROM session_v2 WHERE id=?`, converts millisecond `time_created` (`float(started_at) / 1000.0`) against `ctx.epoch`, and checks `workspace_key(found_cwd) == workspace_key(ctx.cwd)`. - `discover` (`.agents/skills/lib_py/agents/adapters/opencode.py:198-235`) queries `SELECT id FROM session_v2 WHERE parent_id IS NULL`, filtering out subagent child sessions (which share `directory` and have newer `time_created` than the top-level TUI session). - Verified against read-only inspection (`mode=ro`) of the host's migrated `opencode.db` and a fresh `2.0.32` DB in a temporary `HOME`: fresh 2.x databases contain `session_v2` (with `id`, `parent_id`, `directory`, `time_created`) and no `session`/`message`/`part`/`todo`/`session_share` tables. - **`purge_artifacts` and `residual_artifacts`**: - `purge_artifacts` (`.agents/skills/lib_py/agents/adapters/opencode.py:149-181`) checks `self._has_session(db, uuid)` (`SELECT 1 FROM session_v2 WHERE id=?`) before calling `self._run(ctx, ["session", "delete", "--standalone", "--", uuid], timeout=60)`. - Using `--` before `uuid` prevents option-flag injection if an ID ever starts with `-`, and `--standalone` avoids starting or attaching to a per-user background service. - In a fresh 2.0.32 DB, `session_inbox`, `session_message`, `session_pending`, `instruction_entry`, and `instruction_state` all have `ON DELETE CASCADE` foreign keys to `session_v2(id)`, and `opencode session delete` also removes `event` and `event_sequence` rows (`aggregate_id`) for the session and its child sessions while leaving other sessions untouched. - For databases migrated from 1.x, `purge_artifacts` subsequently deletes matching `uuid` rows from `part`/`parts`/`message`/`messages`/`session`/`sessions` (if those tables exist) and `residual_artifacts` reports any surviving `todo.session_id` or `session_v2.id` (e.g., if `session delete` failed or the binary was missing, in which case `purge_artifacts` emits a `WARN:` on `stderr`). ### 1.2 End-to-End Lifecycle (`create` → `reconcile` drift C → `status` → `stop` → `resume`) - **`create_session.sh`, `resume_session.sh`, `update_yaml_resumed.sh`**: - All three scripts and `OpenCodeAgentAdapter.spawn_spec` / `resume_spec` consistently use `opencode --auto --standalone` (and `opencode --session --auto --standalone` on materialized resume), removing the defunct `--agent build` flag rejected by OpenCode 2.x TUI. - **`reconcile.sh` (drift C) and `status.sh`**: - `.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh:955-990` inspects `PRAGMA table_info(session_v2)`, filters by `parent_id IS NULL`, `directory = ?`, and `time_created >= ?` (ms), and validates candidates via `verify_session_uuid(cwd, 'opencode', uuid, s_eval, mode="discover")`. - `.agents/skills/multi-agent-mux-status/scripts/status.sh:127,337` checks `SELECT 1 FROM session_v2 WHERE id=?` in both human and JSON status paths. - **`--standalone` process lifecycle & `stop_session.sh`**: - Verified in an isolated PTY with real `opencode v2.0.32`: `opencode --auto --standalone` spawns `opencode serve --stdio --port 0` as a direct child of the TUI process; closing the PTY master terminates both the TUI and its `opencode serve --stdio --port 0` child cleanly within ~0.1s with zero lingering background processes. - `stop_session.sh`'s graceful `/exit` (`send_keys_safe` with echo wait before `Enter`) -> `herdr kill-session` (closes PTY) -> `SIGKILL` fallback and `in-use` gate (`ps` argv check for `--session `) work cleanly with `--standalone`. ### 1.3 Linux Portability, Shell/Python Compatibility, and Completeness - **Linux & XDG portability**: - `OpenCodeAgentAdapter._run` explicitly sets `HOME`, `HOME_DIR`, and `XDG_DATA_HOME = os.path.join(ctx.home_dir, ".local/share")`, ensuring that even on Linux hosts where `XDG_DATA_HOME` is set in the ambient desktop environment, adapter CLI invocations remain isolated to `ctx.home_dir`. - `test_real_opencode_2x_contract` explicitly unsets `XDG_DATA_HOME`, `XDG_STATE_HOME`, `XDG_CONFIG_HOME`, `XDG_CACHE_HOME`, and `HOME_DIR` via `monkeypatch.delenv`. - **Python 3.9 & Bash 3.2 compatibility**: - Verified on macOS `/usr/bin/python3` / `.venv/bin/python` (3.9.6) and `/bin/bash` (3.2.57 with `set -u`). - **Repository-wide search for stale 1.x assumptions**: - Grepping `.agents/skills/`, `tests/`, and `docs/` for `db path`, `--agent build`, `FROM session\b`, `INTO session\b`, and `table_info(session)` confirms no remaining 1.x code or documentation assumptions exist outside explanatory comments. - **Test coverage & negative regression verification**: - `tests/test_issue60_opencode_v2.py` (10/10 passed, including `test_real_opencode_2x_contract` against real `opencode v2.0.32`), `tests/test_a4_adapter_contract.py`, `tests/test_issue31_purge_residual.py`, `tests/test_issue42_skill_blocks.py`, `tests/test_issue55_agent_homes.py`, and `tests/test_tier1_unit.py` all pass. - Reverting `SELECT id FROM session_v2 WHERE parent_id IS NULL` to `SELECT id FROM session` in an isolated `/tmp` copy immediately fails `test_discover_and_verify_read_session_v2_and_skip_child_sessions` and `test_a_fresh_2x_database_has_no_session_table_and_still_works`. --- ## 2. Non-Blocking Observation (Out of Scope for Issue #60) - **[Note — `stall_watch.py` `background_children` with `--standalone`]**: - Because `opencode --auto --standalone` keeps `opencode serve --stdio --port 0` alive as a child process of the TUI while idle, and `OpenCodeAgentAdapter.resident_children` is `()`, `lib_py.stall_watch.background_children(agent_pid, adapter.resident_children)` sees the `opencode serve --stdio --port 0` child process and conservatively withholds the optional idle nudge (`NOTIFY_IDLE_AND_NUDGE`). If idle nudging is desired for `--standalone` OpenCode in the future, `stall_watch.background_children` can exempt children whose `argv` matches `opencode serve --stdio` (while still walking any tool subprocesses spawned beneath that server). This does not affect any session lifecycle, discovery, or purge behavior in Issue #60. [VERDICT: PASS]