Files
multi-agent-mux/.agents/reports/reviewer-agy-01/report-e95527ef.md
T

75 lines
7.4 KiB
Markdown

# Issue #64 Code Review Report (`3ce5d5f..b0eccc5`)
- **Job ID**: `e95527ef`
- **Reviewer**: `agy` (`herdr:reviewer-agy-01`)
- **Branch**: `fix/issue-64-ro-connect`
- **Base .. Head**: `3ce5d5fdc108b0435e933a251fe1c1256b5d7973` (`v4.9.7`) .. `b0eccc530149bc6e9211727e19921665c7b80492` (1 commit)
- **Verification Worktree**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1`
---
## 1. Root Cause Analysis (Task 1)
**Why did the Issue #55 agent home guard detect newly created `.db-wal` and `.db-shm` sidecars beside another workspace's `agy` conversation DB during a macOS test run?**
1. **Global `agy` conversation directory scan in `reconcile.sh` drift C (`.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh:797-817`)**:
- When an `agy` session is `running` and unpinned (`agy_conversation_id_own` is `None`), `reconcile.sh` globs every `*.db` in `${HOME_DIR}/.gemini/antigravity-cli/conversations/` (a global directory shared across all workspaces under that `HOME`) and invokes `verify_session_uuid(cwd, 'agy', uuid, s_eval, mode="discover")`.
2. **`AgyAgentAdapter.verify_artifact` opened every candidate `.db` with read-write `sqlite3.connect(path)` (`.agents/skills/lib_py/agents/adapters/agy.py:60-80`)**:
- In `discover` mode, `verify_artifact` reads `cache/last_conversations.json`. When `lc_data.get(ctx.cwd) == uuid` is `False` (for example, `uuid` belongs to another workspace), `verify_artifact` only filters out `uuid` if it is in `ctx.row.get("_sibling_claimed_uuids")` (which tracks siblings in the **same** workspace `cwd`) or if `os.path.getmtime(path) < ctx.epoch`.
- For any conversation `.db` from another workspace whose `mtime >= ctx.epoch` (or when `ctx.epoch` is not set), `verify_artifact` proceeded to `conn = sqlite3.connect(path)` to run `SELECT count(*) FROM steps`.
3. **macOS persistent WAL sidecar creation**:
- On macOS, Apple's system `libsqlite3.dylib` sets `SQLITE_FCNTL_PERSIST_WAL = 1` by default. Opening a WAL-mode `.db` that currently has no `-wal`/`-shm` sidecars with read-write `sqlite3.connect(path)` and closing it creates `<uuid>.db-wal` and `<uuid>.db-shm` and leaves them on disk.
- Similarly, if any `reconcile.sh` or `verify_artifact` call ran against the real `HOME` (or if another workspace's `reconcile.sh` / `agy` process scanned `conversations/*.db` while the test suite was running), opening a clean WAL `.db` with `sqlite3.connect(path)` created `<uuid>.db-wal` and `<uuid>.db-shm` in `~/.gemini/antigravity-cli/conversations/`, which `_agent_home_guard` flagged at session teardown.
- Replacing `sqlite3.connect(path)` with `_ro_connect(path)` (`mode=ro&immutable=1` when no `-wal` exists) prevents MAM from ever creating or removing `-wal`/`-shm` sidecars when inspecting `agy` conversation databases (verified by `test_unpinned_agy_reconcile_leaves_other_workspace_clean_db_without_sidecars`, which fails on `base` `3ce5d5f` and passes on `head` `b0eccc5`).
---
## 2. Summary of Changes (`3ce5d5f..b0eccc5`)
### Changed Files & Lines (Read-Only Agent DB Queries)
1. **`.agents/skills/lib_py/agents/base.py:111-145`**:
- Moved `_ro_connect(db: str)` to `lib_py.agents.base` as a shared helper (with `tempfile.TemporaryDirectory(prefix="mam-ro-")` and `os.path.basename(path) or "state.db"` for the `walonly` branch).
- Updated `db_rows_naming` default `connect` to `_ro_connect` (`conn = (connect or _ro_connect)(db)`), so `codex.residual_artifacts` (`state_*.sqlite`, `thread_history_*.sqlite`) and any other `db_rows_naming` callers also use `_ro_connect` instead of plain `mode=ro`.
2. **`.agents/skills/lib_py/agents/adapters/opencode.py:3, 161-185`**:
- Imports and reuses `_ro_connect` from `lib_py.agents.base`.
3. **`.agents/skills/lib_py/agents/adapters/agy.py:3, 74`**:
- `verify_artifact` (and `discover`, which delegates to `verify_artifact`) opens `conversations/<uuid>.db` via `_ro_connect(path)`.
4. **`.agents/skills/lib_py/agents/adapters/codex.py:3, 80, 195`**:
- `_find_rollout_via_db` (used by `_find_rollout`, `artifact_path`, `verify_artifact`) and `discover` open `state_*.sqlite` via `_ro_connect(db)`.
5. **`.agents/skills/lib_py/agents/adapters/hermes.py:3, 51, 100`**:
- `verify_artifact` and `discover` open `~/.hermes/state.db` via `_ro_connect(hdb)`.
6. **`.agents/skills/multi-agent-mux-status/scripts/status.sh:90-91, 103, 120-121, 290-291, 303, 320-321`**:
- Both `--json` and text modes use `_ro_connect` from `lib_py.agents.base` for `hermes` (`hdb`), `opencode` (`odb`), and `codex` (`db`). Zero raw `sqlite3.connect` calls remain in `status.sh`.
7. **`.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh:423, 865, 954, 1047`**:
- Imports `_ro_connect` from `lib_py.agents.base` at top level and uses it for `hermes` drift C (`_ro_connect(hdb)`), `opencode` drift C (`_ro_connect(odb)`), and `codex` drift C (`_ro_connect(state_db)`). Zero raw `sqlite3.connect` calls remain in `reconcile.sh`.
8. **`tests/test_issue64_ro_connect.py` (lines 1-408)**:
- 43 new regression tests covering shared helper identity, all 4 WAL states (`clean`, `leftover`, `walonly`, `writer`) across `agy`, `codex`, `hermes`, and `db_rows_naming`, static and runtime checks of `status.sh` and `reconcile.sh`, and cross-workspace unpinned `agy` drift-C discovery.
### Intentionally Excluded `sqlite3.connect` Call Sites
- **Agent DB write paths (`purge_artifacts`)**:
- `lib_py/agents/adapters/codex.py:152` (`DELETE FROM threads WHERE id=?`)
- `lib_py/agents/adapters/hermes.py:74` (`DELETE FROM sessions` / `DELETE FROM messages`)
- `lib_py/agents/adapters/opencode.py:193` (`DELETE` from 1.x legacy tables)
- **MAM's own state database (`agent-sessions.db`)**:
- `lib_py/atomic_yaml.py:61`, `lib_py/state.py:17`, `lib_py/verify_session.py:41`, `lib_py/loop/runner.py:416`, `lib.sh:1599, 2068`.
---
## 3. Test Results (Base vs. Head) & Remaining Risks
1. **Negative verification on `base` (`3ce5d5f`)**:
- Running `tests/test_issue64_ro_connect.py` against an archive copy of `base` (`3ce5d5f`) on macOS: **`23 failed, 20 passed in 10.97s`** (every `clean` and `walonly` state across `agy`, `codex`, `hermes`, `db_rows_naming`, `status.sh`, `reconcile.sh`, plus `test_ro_connect_lives_in_base_and_opencode_reuses_it`, `test_status_and_reconcile_static_ro_connect_usage`, and `test_unpinned_agy_reconcile_leaves_other_workspace_clean_db_without_sidecars` failed as expected).
2. **Verification on `head` (`b0eccc5`)**:
- `tests/test_issue64_ro_connect.py`: **43 passed**
- `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_issue55_agent_homes.py`: **11 passed**
- `tests/test_issue31_purge_residual.py`: **67 passed**
- **Total**: **`221 passed in 119.66s`**
- `git status -s` and `git diff --check` in the verification worktree are completely clean.
3. **Remaining risks**:
- External processes outside MAM (such as an active `agy` IDE/CLI session running in another workspace on the same host) can still create or remove their own conversation `.db-wal`/`.db-shm` sidecars in `~/.gemini/antigravity-cli/conversations/` while a pytest session is running, which `_agent_home_guard` (which only exempts live non-temp workspace directories for `claude`) can observe as a new directory entry. Within MAM itself, all read-only queries against `agy`, `codex`, `hermes`, and `opencode` databases are now sidecar-safe.
[VERDICT: PASS]