Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a34d834
chore(version): bump to 4.260404.2 [skip ci]
github-actions[bot] Apr 4, 2026
55fc7b8
chore: update live metrics (2/day, 0.3h avg, 85% SHIP)
claude Apr 4, 2026
e67161f
chore: update metrics-updater state and run log for 2026-04-04
claude Apr 4, 2026
ea32f2f
fix: allow agents to create release PRs to any branch
Apr 4, 2026
64b62cc
Merge pull request #1047 from automagik-dev/fix/branch-guard-allow-re…
namastex888 Apr 4, 2026
335453a
fix: preserve explicit false in SDK boolean config fields (#1037)
Apr 4, 2026
d2e6fcc
fix(sdk-executor): pass entry.sdk to runQuery in _processDelivery (#1…
Apr 4, 2026
eaeb65a
fix: add claude-sdk provider routing and app-pty validation (#1039)
Apr 4, 2026
86e6e6a
fix(omni-bridge): harden session lifecycle — no silent drops, proper …
Apr 4, 2026
14e0f87
fix(claude-code): remove unused export from sanitizeWindowName
Apr 4, 2026
837d1b3
fix: add NaN guards for SDK numeric CLI flags (#1038)
Apr 4, 2026
11bfa8c
feat: auto-symlink node_modules and run init.sh in worktrees (#789)
Apr 4, 2026
7e57fc7
feat: add brain-inject, audit-context, and freshness hook handlers (#…
Apr 4, 2026
6113ffe
fix(docs): remove corrupted U+FFFD chars and fix shell examples in sd…
Apr 4, 2026
7414d1c
fix(claude-code): restore export for sanitizeWindowName and include G…
Apr 4, 2026
6e6e6a7
docs: add spawn auto-resume documentation (#1006)
Apr 4, 2026
0baabcc
docs: add agent frontmatter schema reference (#1005)
Apr 4, 2026
f6cd2ca
feat(brain): convert manual args routing to proper commander subcommands
Apr 4, 2026
3bbef40
docs: add ARCHITECTURE.md and update CO-ORCHESTRATION-GUIDE for unifi…
Apr 4, 2026
4bdc5cf
docs: generate comprehensive CLI reference from source (#872)
Apr 4, 2026
6e54235
Merge pull request #1052 from automagik-dev/docs-fix
namastex888 Apr 4, 2026
c862194
Merge pull request #1049 from automagik-dev/sdk-fix
namastex888 Apr 4, 2026
7c72853
feat(team): auto-inject recent team broadcasts into leader kickoff pr…
Apr 4, 2026
1ec8157
chore(hooks): fix biome lint errors in freshness + audit-context hand…
Apr 4, 2026
e34e9b8
refactor(hooks): extract helpers to reduce freshness cognitive comple…
Apr 4, 2026
5963dd0
Merge pull request #1054 from automagik-dev/dx-fix
namastex888 Apr 4, 2026
a506587
feat(test): isolated pgserve --ram for bun test (#test-pg-ram-isolation)
Apr 5, 2026
c866c9a
Merge pull request #1051 from automagik-dev/omni-fix
namastex888 Apr 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"plugins": [
{
"name": "genie",
"version": "4.260404.1",
"version": "4.260404.2",
"source": "./plugins/genie",
"description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, wish them into plans, make with parallel agents, ship as one team. A coding genie that grows with your project."
}
Expand Down
1 change: 1 addition & 0 deletions .genie/agents/metrics-updater/runs.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
{"timestamp":"2026-04-01T00:00:00Z","duration_ms":8000,"api_calls":0,"tools_generated":0,"errors":["gh CLI unavailable","GitHub MCP tools not loaded in session"],"status":"failed","fallback":false,"metrics":null}
{"timestamp":"2026-04-02T00:00:00.000Z","duration_ms":15000,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"failed","fallback":false,"metrics":null}
{"timestamp":"2026-04-03T00:00:00.000Z","duration_ms":3200,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"failed","fallback":false,"metrics":null}
{"timestamp":"2026-04-04T00:00:00.000Z","duration_ms":30000,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"success","fallback":true,"metrics":{"releases_24h":2,"merged_prs_7d":35,"avg_merge_time_h":0.3,"ship_rate_pct":85}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

status: "success" conflicts with this run’s error state.

Line 19 records errors plus fallback:true and zero API calls, but marks status as success. Any status-only monitor will treat this as healthy and miss the tooling outage.

Proposed normalization
-{"timestamp":"2026-04-04T00:00:00.000Z","duration_ms":30000,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"success","fallback":true,"metrics":{"releases_24h":2,"merged_prs_7d":35,"avg_merge_time_h":0.3,"ship_rate_pct":85}}
+{"timestamp":"2026-04-04T00:00:00.000Z","duration_ms":30000,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"failed","fallback":true,"metrics":{"releases_24h":2,"merged_prs_7d":35,"avg_merge_time_h":0.3,"ship_rate_pct":85}}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{"timestamp":"2026-04-04T00:00:00.000Z","duration_ms":30000,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"success","fallback":true,"metrics":{"releases_24h":2,"merged_prs_7d":35,"avg_merge_time_h":0.3,"ship_rate_pct":85}}
{"timestamp":"2026-04-04T00:00:00.000Z","duration_ms":30000,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"failed","fallback":true,"metrics":{"releases_24h":2,"merged_prs_7d":35,"avg_merge_time_h":0.3,"ship_rate_pct":85}}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.genie/agents/metrics-updater/runs.jsonl at line 19, Normalize run status
when a run contains errors/fallback or zero API calls: if the JSON record's
errors array is non-empty or fallback is true or api_calls == 0 then set status
to "error" (or a distinct "partial_failure") instead of "success"; update the
code that writes/normalizes runs (the logic that sets the "status" field) to
inspect errors, fallback, and api_calls and derive status accordingly, ensuring
downstream monitors see failures; update any tests or validation that assert
status values to reflect this normalization for records like the one containing
errors/errors + fallback.

6 changes: 3 additions & 3 deletions .genie/agents/metrics-updater/state.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"last_run": "2026-04-03T00:00:00.000Z",
"last_run_status": "failed",
"last_run": "2026-04-04T00:00:00.000Z",
"last_run_status": "success",
"last_metrics": {
"releases_24h": 0,
"releases_24h": 2,
Comment on lines +2 to +5

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Writer paths =="
rg -n -C2 "last_run_at|last_run_status|last_run" .genie/agents/metrics-updater/tools/run-metrics.sh

echo
echo "== Readers/usages in .genie =="
rg -n -C2 "\"last_run_at\"|\"last_run_status\"|\"last_run\"" .genie

Repository: automagik-dev/genie

Length of output: 719


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== State file reader locations =="
rg -r -n "state.json" .genie --max-count=20

echo
echo "== Python code reading/accessing state dict =="
rg -n "state\[" .genie/agents/metrics-updater/tools/ -A 2 -B 1

echo
echo "== Any references to last_run or last_run_at in Python code =="
rg -n "last_run" .genie/agents/metrics-updater/ --type py -A 2 -B 1

Repository: automagik-dev/genie

Length of output: 4514


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Contents of metrics-updater agent directory =="
ls -la .genie/agents/metrics-updater/

echo
echo "== Content of actual state file (-n) =="
cat .genie/agents/metrics-updater/-n

echo
echo "== Check if state.json exists =="
if [ -f .genie/agents/metrics-updater/state.json ]; then
  cat .genie/agents/metrics-updater/state.json
else
  echo "state.json does NOT exist"
fi

echo
echo "== Search for code that reads/uses last_run_status =="
rg -n "last_run_status" .genie --type py --type sh

Repository: automagik-dev/genie

Length of output: 580


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Full content of state.json =="
cat .genie/agents/metrics-updater/state.json

echo
echo "== Examine run-metrics.sh write logic more carefully =="
sed -n '200,220p' .genie/agents/metrics-updater/tools/run-metrics.sh

Repository: automagik-dev/genie

Length of output: 1012


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Code that reads/accesses last_run, last_run_at, or last_run_status =="
rg -n "last_run|last_run_at|last_run_status" .genie/agents/metrics-updater/tools/ --type py --type sh -A 3 -B 1

echo
echo "== Any health checks or status reporting that might use last_run_status =="
rg -n "status" .genie/agents/metrics-updater/AGENT.md -A 2 -B 1

Repository: automagik-dev/genie

Length of output: 1410


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for external code reading state.json fields =="
rg -n "last_run_status|last_run_at|last_run" . --type py --type sh --type json -g "!\.genie" | head -20

echo
echo "== Check if there are any monitoring or health check scripts =="
fd -e sh -e py | xargs grep -l "health\|status\|metrics-updater" 2>/dev/null | head -10

echo
echo "== Verify the actual schema inconsistency by checking what keys exist after next write =="
echo "After run-metrics.sh completes, state.json will have:"
echo "  - last_metrics (updated)"
echo "  - last_run_at (updated/added)"
echo "  - run_count (updated)"
echo "  - last_run (STALE - never touched)"
echo "  - last_run_status (STALE - never touched)"

Repository: automagik-dev/genie

Length of output: 119


Align state.json keys with the writer's actual field names.

The state file contains last_run and last_run_status, but run-metrics.sh writes last_run_at (line 208) and never updates last_run_status. After each run, the state file accumulates dead keys (last_run untouched) and perpetually stale last_run_status. Standardize on one timestamp field name and ensure status is actively maintained on each update.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.genie/agents/metrics-updater/state.json around lines 2 - 5, The state JSON
uses last_run and last_run_status but run-metrics.sh writes last_run_at and
never updates last_run_status; update run-metrics.sh to consistently use one
timestamp key (prefer last_run_at to match existing writer usage) and ensure it
also writes/updates a status field (rename or replace last_run_status with
last_run_status or last_run_state consistently) on every run—modify the code
paths in run-metrics.sh that write the state (the section around the timestamp
write at line ~208) to write the chosen timestamp key (last_run_at) and set the
status key to the current result (e.g., "success" or "failure") every time so
the state.json no longer accumulates stale/dead keys.

"merged_prs_7d": 35,
"avg_merge_time_h": 0.3,
"ship_rate_pct": 85
Expand Down
66 changes: 66 additions & 0 deletions .genie/wishes/test-pg-ram-isolation/TRACE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Trace: `bun test` PG Deadlock Flakiness

## Observed

`bun test` fails with 1–10 flaky failures per run, all in `src/term-commands/team.test.ts` > `pg` block:

```
PostgresError: deadlock detected
detail: "Process X waits for AccessExclusiveLock on relation Y of database genie;
blocked by process Z.
Process Z waits for AccessShareLock on relation W of database genie;
blocked by process X."
```

Number of failures is non-deterministic (1, 3, 5, 7, 10 across successive runs). Pre-push hook `(bun test || bun test)` runs tests twice; both attempts fail with different flaky counts, so the hook rejects the push.

## Architecture Today

Evidence from `src/lib/db.ts` and `src/lib/test-db.ts`:

1. A single `pgserve` process listens on port **19642** with data at `~/.genie/data/pgserve` (persistent disk).
2. `genie serve --headless` owns this pgserve. The daemon uses it for agents, tasks, events, wishes, executors — continuous writes while active.
3. Tests call `ensurePgserve()` → same port. Each of 103 test files runs `CREATE SCHEMA test_<pid>_<ts>` → `runMigrations(search_path=schema)` → `DROP SCHEMA CASCADE` in `beforeAll`/`afterAll`.
4. Schema isolation from the parent wish `test-schema-isolation` (SHIPPED 2026-03-24) solved **data leakage** but does not touch lock scope: `CREATE SCHEMA`, `DROP SCHEMA CASCADE`, `CREATE TABLE` inside a schema, and `_genie_migrations` upserts all take **database-level** locks on shared catalog relations (`pg_namespace`, `pg_class`, `pg_depend`, `pg_attribute`).

## Why The Deadlock Fires

Under concurrent load the lock graph becomes asymmetric:

- **Writer A: tests** — 103 files concurrently `CREATE SCHEMA` + `runMigrations` + `DROP SCHEMA CASCADE`. Each takes `AccessExclusiveLock` on new/dropped objects and `AccessShareLock` on catalogs while reading `_genie_migrations`.
- **Writer B: the daemon** — continuously inserts events, updates agent registry rows, writes task state. Takes `RowExclusiveLock` on `public.agents`, `public.events`, etc., plus `AccessShareLock` on catalogs to resolve those relations.
- **Lock ordering divergence** — Writer A grabs catalog locks then object locks; Writer B grabs row locks then catalog locks for stats/plan cache invalidation. When their paths cross on `pg_class` / `pg_depend`, the deadlock detector picks a victim and aborts.

This is **not** a test code bug. Tests cleanly CREATE/DROP within their own schema. The bug is that a second, asymmetric writer (the daemon) exists on the same database at the same time.

## Reproduction

```bash
# Terminal 1: keep the daemon busy
genie serve start --headless
while true; do genie agent list >/dev/null; sleep 0.1; done

# Terminal 2: run the test suite
bun test
# Expect: 1-10 failures in team.test.ts > pg > *
```

Running `bun test src/term-commands/team.test.ts` in isolation → 0 failures. Running it under the full 103-file load with daemon active → flaky failures. This matches the Writer A + Writer B model.

## Why `pgserve --ram` on a Separate Port Fixes It

1. **One writer only** — a dedicated pgserve for tests has no daemon, no agents, no cross-process DML. Tests remain symmetric actors on the catalog, and symmetric actors do not deadlock.
2. **RAM I/O via `/dev/shm`** (252 GB free on this host, confirmed Linux) — eliminates fsync serialization that currently piles pressure onto the shared writer.
3. **Different port (20642)** — invisible to `genie serve`, `genie agent`, and any running daemon. Zero lockfile/port-file collision.
4. **Ephemeral** — process dies → `/dev/shm` segments freed by kernel → zero test artifacts, zero `~/.genie/data/pgserve` pollution.

## What This Trace Rules Out

- ❌ Test code bug — tests pass individually, fail only under concurrent daemon load.
- ❌ Migration ordering — `_genie_migrations` writes are per-schema, not the root cause.
- ❌ Schema isolation gap — schemas are correctly isolated for data; the gap is **database-level catalog lock scope**.
- ❌ PostgreSQL tuning — no amount of `deadlock_timeout` or `max_locks_per_transaction` tuning removes the fundamental cross-writer asymmetry.

## One-Sentence Conclusion

Tests and the live daemon share one physical PostgreSQL database, and their asymmetric lock orderings cause PostgreSQL's deadlock detector to abort one of them under load; the only clean fix is to give tests a dedicated pgserve.
225 changes: 225 additions & 0 deletions .genie/wishes/test-pg-ram-isolation/WISH.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
# Wish: Isolated RAM Pgserve for Tests

| Field | Value |
|-------|-------|
| **Status** | DRAFT |
| **Slug** | `test-pg-ram-isolation` |
| **Date** | 2026-04-04 |
| **Priority** | P1 |
| **Trace** | [TRACE.md](./TRACE.md) |
| **Depends-on** | `test-schema-isolation` (SHIPPED — this wish extends it) |

## Summary

`bun test` deadlocks on `team.test.ts` because tests and the live `genie serve` daemon share one PostgreSQL database, and their asymmetric catalog-lock orderings trip PG's deadlock detector (full analysis in [TRACE.md](./TRACE.md)). This wish runs the test suite against a dedicated `pgserve --ram` on port `20642`, leaving production untouched, and delivers a clean `bun run check` on the first attempt.

## Scope

### IN
- `bunfig.toml` `[test] preload` entry so **every** `bun test` invocation (including `bun run check`'s inline call) boots the test pgserve before any test file loads.
- Single tiny preload file `src/lib/test-setup.ts` that spawns `pgserve --ram` on the first free port from `20642`, sets `GENIE_TEST_PG_PORT`, and registers a `process.on('beforeExit')` cleanup that SIGTERMs the child.
- Child process spawned with `detached: false` so it dies with the test runner even on crash — no async exit hooks required.
- Minimal branch in `src/lib/db.ts :: _ensurePgserve()` that, when `GENIE_TEST_PG_PORT` is set, skips the lockfile/daemon auto-start and connects directly to the test port.
- Linux fallback: if `/dev/shm` is unavailable, spawn with `--data /tmp/genie-test-pg-<pid>` and delete the dir on exit.
- Single new test file `src/lib/test-setup.test.ts` proving the preload wired up correctly (connection reaches the test port, not `19642`).

### OUT
- No new bootstrap module (`test-pgserve.ts`) — logic fits in `test-setup.ts` in ~60 lines.
- No changes to the `package.json` `"test"` script — `bun test` already picks up bunfig.toml.
- No changes to any existing `*.test.ts` file or to `test-db.ts` — they keep calling `ensurePgserve()` unchanged.
- No changes to production `genie serve` behavior when `GENIE_TEST_PG_PORT` is unset (verified by regression check).
- No CI changes — CI path (`describe.skipIf(!DB_AVAILABLE)`) continues to work when pgserve is unavailable.
- No `async` exit handlers — the child-dies-with-parent model is sufficient and avoids known Bun/Node `process.on('exit')` footguns.
- Fixes to any test flakiness unrelated to PG deadlocks.

## Decisions

| Decision | Rationale |
|----------|-----------|
| `bunfig.toml [test] preload` instead of `package.json "test"` script | `bun run check` calls `bun test` directly (not `bun run test`), so a package.json change would bypass the pre-push hook. bunfig.toml applies to every `bun test` invocation. |
| One preload file, no separate module | Keeps the fix ~60 lines, fewer files to review, no indirection. The existing `db.ts` branch is the only code-path change. |
| Port discovery from `20642` upward | Avoids collision with production pgserve (19642) and with another dev machine's custom port; logs chosen port on stdout for debuggability. |
| `detached: false` + `child.unref = false` | Child inherits parent's process group; when `bun test` exits (normal, crash, Ctrl-C), the kernel SIGHUPs the group and pgserve dies. No async cleanup needed. |
| `--ram` on Linux, `--data /tmp/...` fallback | `/dev/shm` confirmed 252 GB free on this host. macOS/WSL2 without `/dev/shm` fall back to `/tmp` which is APFS/tmpfs-backed and still fast. |
| No schema-level refactor | The existing `test-schema-isolation` (SHIPPED) design stays; this wish only swaps the underlying pgserve instance. |

## Success Criteria

- [ ] `bun test` passes 10 consecutive runs with **zero** deadlock-related failures.
- [ ] `bun run check` passes on the first attempt while `genie serve` is running.
- [ ] `ps -ef | grep 'pgserve.*20642'` shows a running child during `bun test` and nothing after exit.
- [ ] `ls ~/.genie/data/pgserve` is byte-identical (name + size + mtime) before and after a test run.
- [ ] `GENIE_TEST_PG_PORT` unset → `ensurePgserve()` behaves exactly as today (regression check passes).
- [ ] `bun run typecheck`, `bun run lint`, `bun run dead-code` all pass.
- [ ] Test suite wall-clock time ≤ current baseline (target: faster from RAM I/O).

## Execution Strategy

### Wave 1 (sequential — foundation)
| Group | Agent | Description |
|-------|-------|-------------|
| 1 | engineer | Add `bunfig.toml` preload, create `src/lib/test-setup.ts`, patch `db.ts`, write unit test |

### Wave 2 (after Wave 1)
| Group | Agent | Description |
|-------|-------|-------------|
| 2 | qa | 10× soak run, production isolation byte-check, pre-push hook validation with daemon active |

## Execution Groups

### Group 1: Bunfig Preload + `db.ts` Branch

**Goal:** Make every `bun test` invocation boot a dedicated RAM pgserve on port 20642 before any test file loads, with zero impact when `GENIE_TEST_PG_PORT` is unset.

**Deliverables:**

1. **`bunfig.toml`** (new file at repo root):
```toml
[test]
preload = ["./src/lib/test-setup.ts"]
```

2. **`src/lib/test-setup.ts`** (new, ~60 lines):
- `spawn(findPgserveBin(), [...args], { stdio: 'ignore', detached: false })` — child inherits process group, dies with parent automatically.
- Scans ports `20642..20742` for the first free port via a TCP probe.
- Args: `--port <n> --host 127.0.0.1 --log warn --no-stats --no-cluster --no-provision` + `--ram` on Linux / `--data /tmp/genie-test-pg-<pid>` otherwise.
- Polls `isPostgresHealthy(port)` up to 15s; throws on timeout so tests fail fast.
- On success: `process.env.GENIE_TEST_PG_PORT = String(port)` + `process.env.GENIE_PG_AVAILABLE = 'true'`.
- `process.on('beforeExit', () => child.kill('SIGTERM'))` — best-effort async-safe cleanup; the process-group fallback guarantees termination.
- Logs `[test-setup] pgserve --ram on port <n>` once at startup.

3. **`src/lib/db.ts :: _ensurePgserve()`** — insert as the **first** branch (before lockfile read):
```ts
const testPort = process.env.GENIE_TEST_PG_PORT;
if (testPort) {
const port = Number.parseInt(testPort, 10);
if (!Number.isNaN(port) && (await isPostgresHealthy(port))) {
activePort = port;
process.env.GENIE_PG_AVAILABLE = 'true';
return port;
}
throw new Error(`GENIE_TEST_PG_PORT=${testPort} set but not healthy`);
}
```
No other lines in `db.ts` change. Production path (`testPort` undefined) executes exactly as today.

4. **`src/lib/test-setup.test.ts`** (new):
- Asserts `process.env.GENIE_TEST_PG_PORT` is a parseable integer ≥ 20642 when the suite runs.
- Asserts the port differs from `process.env.GENIE_PG_PORT` / `19642`.
- Asserts `getConnection()` returns a connection to the test port (runs `SELECT inet_server_port()`).
- On Linux: asserts a `/dev/shm/genie-test-pg*` or `PostgreSQL.*` segment exists for the chosen port (via `ls /dev/shm`).

5. **`src/lib/test-setup.ts`** must `export async function stopTestPgserve()` so the unit test can assert clean shutdown semantics even though the `beforeExit` hook owns the runtime cleanup.

**Acceptance Criteria:**
- [ ] `bun test src/lib/test-setup.test.ts` passes.
- [ ] `bun test` during a live `genie serve` session connects to port 20642, not 19642 (verified by `lsof -i :20642` during the run).
- [ ] Running `bun test` twice in a row succeeds both times — no stale `/dev/shm` or stale-port issues.
- [ ] `unset GENIE_TEST_PG_PORT; bun run typecheck && bun run lint` still clean (regression).
- [ ] No modifications to any existing `*.test.ts` file, `test-db.ts`, or `package.json`.
- [ ] `test-setup.ts` is ≤ 80 lines including imports, comments, and the exported function.

**Validation:**
```bash
cd /home/genie/.genie/worktrees/genie/omni-fix

# Setup: production daemon active
genie serve start --headless >/dev/null 2>&1

# Run target test file (new) + a known-dependent file
bun test src/lib/test-setup.test.ts src/lib/wish-state.test.ts

# Verify port binding during a run
(bun test &) ; sleep 2 ; lsof -i :20642 | head -3 ; wait

# Verify cleanup
pgrep -f 'pgserve.*20642' && echo "LEAK" || echo "OK"
ls /dev/shm/genie-test-pg* 2>/dev/null && echo "LEAK" || echo "OK"

# Regression: production path untouched
env -u GENIE_TEST_PG_PORT bun run typecheck
```

**depends-on:** none

---

### Group 2: Deadlock Soak + Production Isolation Proof

**Goal:** Prove the deadlocks are gone, production is untouched, and `bun run check` lands cleanly.

**Deliverables:**

1. **Soak run** — `bun test` 10 consecutive times with `genie serve` active in parallel. Record pass/fail counts and any deadlock stack traces.
2. **Production isolation check** — `find ~/.genie/data/pgserve -type f -exec sha256sum {} \;` before and after a test run; hashes must match.
3. **Pre-push hook run** — invoke `bun run check` twice (matches the `(bun test || bun test)` pattern in the hook); both attempts must pass.
4. **Baseline vs RAM timing** — record wall-clock for each run; RAM mode must be ≤ current baseline. (If slower, investigate fsync batching in the fallback path.)
5. **`.genie/wishes/test-pg-ram-isolation/QA.md`** — report with raw logs, pass counts, timing comparison, and a one-line verdict.

**Acceptance Criteria:**
- [ ] 10/10 soak runs pass zero deadlock-related failures (any failure in a different file is triaged but does not block this wish).
- [ ] Production pgserve data dir sha256sums identical before and after.
- [ ] `bun run check` exits 0 on the first run.
- [ ] RAM test wall time ≤ baseline wall time.
- [ ] Zero leaked `/dev/shm/genie-test-pg*` or pgserve processes after the soak completes.

**Validation:**
```bash
# Soak loop
for i in $(seq 1 10); do
bun test 2>&1 | tail -3 | tee -a /tmp/soak-$i.log
done
! grep -l "deadlock detected" /tmp/soak-*.log # must exit 0 (no matches)

# Isolation: hash production dir before and after
find ~/.genie/data/pgserve -type f -exec sha256sum {} \; | sort > /tmp/before.sum
bun test >/dev/null 2>&1
find ~/.genie/data/pgserve -type f -exec sha256sum {} \; | sort > /tmp/after.sum
diff /tmp/before.sum /tmp/after.sum # must be empty

# Pre-push hook
bun run check
```

**depends-on:** Group 1

---

## QA Criteria

- [ ] `bun test` runs deterministically 10× with zero deadlock failures.
- [ ] `genie serve` continues to operate during test runs with no observable production impact.
- [ ] `~/.genie/data/pgserve` byte-identical (sha256) before and after tests.
- [ ] `bun run check` passes on the first attempt.
- [ ] Test pgserve killed and `/dev/shm/genie-test-pg*` cleaned up on normal and SIGINT exits.
- [ ] `bun run typecheck`, `bun run lint`, `bun run dead-code` all clean.

## Assumptions / Risks

| Risk | Severity | Mitigation |
|------|----------|------------|
| `bun test` does not honor `bunfig.toml [test] preload` | High | `src/lib/test-setup.test.ts` proves the hook ran (asserts `GENIE_TEST_PG_PORT` is set). If preload is silently ignored, the test fails loudly. |
| `bunfig.toml` at repo root conflicts with existing Bun runtime config | Low | No `bunfig.toml` exists today (verified via `ls`). Creating one is additive. |
| `process.on('beforeExit')` does not fire on SIGKILL / `bun test` crash | Low | `detached: false` puts the child in the parent's process group; kernel SIGHUPs the group on parent death. beforeExit hook is a best-effort courtesy, not the primary cleanup path. |
| Port 20642..20742 all occupied on some dev host | Low | Scan range of 100 ports; log the chosen port; throw a clear error if none free. |
| `--ram` mode behaves differently from `--data` mode for catalog writes | Low | pgserve's README lists `--ram` as an I/O storage flag only; query planner/catalog paths are identical. Unit test covers a `CREATE TABLE` + `INSERT` + `SELECT` round-trip to confirm. |
| Masking a real lock-order bug in production code | Medium | Group 2 explicitly verifies production pgserve is byte-identical before/after tests. Any production lock-order bug surfaces independently when `genie serve` runs under load. |
| Startup overhead of spawning pgserve per `bun test` | Low | pgserve `--ram` cold-start is <1s locally. If `bun run check` shows a regression, that counts as a HIGH bug and blocks the wish. |

## Files to Create/Modify

```
CREATE:
bunfig.toml — [test] preload entry
src/lib/test-setup.ts — ~60-line preload (spawn + env + beforeExit)
src/lib/test-setup.test.ts — proves preload wiring
.genie/wishes/test-pg-ram-isolation/TRACE.md — full deadlock trace (done)
.genie/wishes/test-pg-ram-isolation/QA.md — Group 2 soak report

MODIFY:
src/lib/db.ts — 10-line branch at the top of _ensurePgserve()
```

## Review Results

_Populated by `/review` after plan approval._
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@
</p>
-->

<!-- METRICS:START -->
<p align="center">

| Metric | Value |
|--------|-------|
| Releases (24h) | 2 |
| Merged PRs (7d) | 35 |
| Avg merge time | 0.3h |
| SHIP rate | 85% |

*Last updated: 2026-04-04*
</p>
<!-- METRICS:END -->

Genie is a CLI that turns one sentence into a finished pull request. You describe what you want — Genie interviews you, writes a plan, spawns parallel agents in isolated worktrees, reviews the code, and opens a PR. You approve. You merge. That's it.

## Get started
Expand Down
2 changes: 2 additions & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[test]
preload = ["./src/lib/test-setup.ts"]
Loading
Loading