Skip to content

chore: rolling promotion dev → main (native pgserve-v3 fix + recovery runbook) - #2459

Merged
namastex888 merged 10 commits into
mainfrom
dev
May 20, 2026
Merged

namastex888 merged 10 commits into
mainfrom
dev

Conversation

@namastex888

@namastex888 namastex888 commented May 19, 2026 •

Copy link
Copy Markdown
Contributor

Public release: native genie ↔ autopg/pgserve-v3 + recovery runbook

Rolls dev into main so the stable channel (latest.json) ships the full v3 story to the public. The rolling-pr.yml has been failing on its history-sync check; opening this manually so we don't gate the public release on the cron.

What this brings to stable

  • The fix — embed migrations into the compiled binary + native DB provisioning on autopg/pgserve v3 (with the CodeRabbit/Codex review fixes: v3-runtime.json gate, decoupled from GENIE_ROLE_CUTOVER). Already merged to main directly via fix(db): native genie → autopg/pgserve-v3 migration (embed migrations + self-provision DB) #2456 (same content; cherry-picked onto dev via fix(db): sync native pgserve-v3 migration fix to dev (already on main via #2456) #2457 so dev wasn't behind).
  • UPGRADING-pgserve-v3.md — the upgrade guide covering the three host states (v1.x / v2.x / v3), the only manual data step (backup→restore), the migrate-v1 --no-archive v3 gotcha + half-archive recovery, and the post-upgrade work-recovery runbook (genie status --json 3-bucket triage → genie resume from throwaway tmux → genie agent recover → claude --resume direct fallback). Validated end-to-end on a real "very crowded" instance (31 agents, 132 teams, 2725 sessions).
  • The dev-channel auto-version bumps that have accumulated.

Backward-compat reminder (already validated)

  • v2 users are NOT retargeted: my fix's DB-provisioning is gated strictly on <socketDir>/runtime.json (v3-only marker; v2 publishes admin.json), so existing app_genie_<fp> setups stay on postgres/router untouched.
  • v1 users keep the legacy TCP fallback; the genie db migrate-v1 informational prompt appears but doesn't block.
  • Data-bearing v3 movers get the documented backup→restore step.

After merging, version.yml will fire on main (commit message contains /dev, satisfying the auto-version gate) → tag → release.yml → release-publish → latest.json flips to the new stable carrying the fix + doc.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores

    • Version bumped to 4.260519.3 across package manifests and plugin configuration.
    • Build system improvements for consistent migration handling during compilation.
  • Bug Fixes

    • Enhanced database connection provisioning for v3 runtime environments with improved socket-mode support.

Review Change Stack

claude and others added 10 commits May 19, 2026 12:05
… + self-provision DB)

genie could not migrate onto autopg/pgserve v3. Three coupled defects,
all reproduced + fixed against a live autopg v3 host:

1. KEYSTONE — migrations absent from the compiled binary.
   db-migrations.ts loaded SQL via readdirSync(import.meta.dir
   /../db/migrations) + Bun.file(); `bun build --compile` does NOT bundle
   those runtime FS reads, so the shipped binary saw ZERO migrations →
   `genie db migrate` reported "Applied: 0" on an empty DB, schema never
   created. Fix: scripts/gen-migrations-manifest.ts →
   src/db/migrations.generated.ts (static `with { type: 'text' }` imports
   Bun embeds); loader prefers the embedded set, FS scan kept as a dev
   fallback; build-binary.sh regenerates pre-compile so it can't go
   stale; src/types/sql.d.ts ambient for the text import.

2. autopg v3 not detected as a direct postmaster.
   readPostmasterDiscovery() only read `<socketDir>/admin.json`; autopg
   v3 writes live discovery to `<socketDir>/runtime.json` (admin.json
   moved to ~/.autopg/). directPostmaster was always false → genie stayed
   on the legacy accept-hook/`postgres` path pgserve v3 deleted. Fix:
   read runtime.json (v3) then admin.json (v2), shared parse/validate.

3. No native DB provisioning. resolveDatabaseName() always returned
   `postgres`, relying on the deleted pgserve-v2 accept-hook to
   auto-create+route `app_<name>_<fp>`. Fix: on the direct-postmaster
   path target NATIVE_DB_NAME ('genie'; GENIE_DB_NAME override) and
   ensureDatabaseExists() creates it from the `postgres` maintenance DB
   (allowlisted ident; duplicate_database race tolerated). `database` is
   reassigned in-place so the bootstrap pool, role-cutover GRANTs and
   migrations agree on the target DB (an earlier split granted the scoped
   role in `postgres` while the pool ran in `genie` → "no schema has been
   selected to create in"). Legacy router path unchanged.

Proven on a live autopg v3 host, freshly compiled binary, zero env hacks
/ zero manual DB creation: genie db migrate → provisioned database
"genie" → role-cutover database=genie → 63 migrations → pg-seed 132
teams → genie db = 63 migrations / 52 tables → `genie ls` reads PG
cleanly. biome + tsc clean on changed files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ple from role-cutover

CodeRabbit (Major) + Codex (P1) both correct:
- Native-DB provisioning was gated on `directPostmaster`, which is
  `cutoverEnabled && … && readPostmasterDiscovery()`. With
  GENIE_ROLE_CUTOVER=0 it was skipped → v3 fell back to `postgres` and
  re-broke (CodeRabbit).
- readPostmasterDiscovery() now also matches v2 `admin.json`, so
  `directPostmaster` was true on pgserve v2 too → my branch retargeted v2
  hosts from `postgres` to `genie`, booting them against a new empty DB
  (Codex P1 regression).

Fix: new `hasV3RuntimeDiscovery()` — true ONLY when `<socketDir>/
runtime.json` (the v3, router-less marker; v2 publishes only admin.json)
parses. Native-DB now keys off `v3NativeDb = transport.useSocket &&
hasV3RuntimeDiscovery()` — NOT cutoverEnabled, NOT v2 admin.json.
`directPostmaster` (role-cutover) is left exactly as-is, so v2 + v3
role-cutover behavior is unchanged.

Validated on a live v3 host, freshly compiled binary:
- default (cutover ON): 63 migrations → `genie` DB ✓
- GENIE_ROLE_CUTOVER=0: 63 migrations → `genie` DB ✓ (no postgres fallback)
- v2 (no runtime.json): v3NativeDb=false → stays on `postgres` (router path)
check:fast (typecheck/lint/dead-code/skills/wishes/emit) + biome green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per "ship now + document backup→restore": covers the three host states
new genie may encounter (v1.x, v2.x, v3) and the only manual step
(snapshot+restore around the v3 cutover for data-bearing users). Notes
the strict runtime.json detection (v1/v2 never retargeted), the
GENIE_DB_NAME / GENIE_ROLE_CUTOVER=0 escape hatches, and roll-back.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-dev

fix(db): sync native pgserve-v3 migration fix to dev (already on main via #2456)
The previous doc covered the DATA layer (backup→restore, in-place v1
import). It did not cover the WORK layer — restoring DB rows does NOT
auto-reattach the live tmux panes / in-flight Claude conversations the
operator was working in. Codifies the recovery runbook validated on a
"very crowded" real instance (31 agents, 132 teams, 2725 sessions):

- 3-bucket triage via `genie status --json` (ok / auto_resume_disabled /
  no_session_id) with the action per bucket
- `genie resume` from a throwaway tmux socket (the dir:/GENIE_TEAM
  gotchas), validated working end-to-end
- `genie agent recover` for ephemeral workers (and why the name-based
  heuristic often misses when customName is null)
- `claude --resume <session-uuid>` direct fallback when genie can't
  pair the JSONL — the conversation data is durable on the filesystem
- The post-mass-resume cosmetic `stale_spawn_dead_pane` reconciler
  glitch (one repeat resume heals it)

Also documents the `migrate-v1` v3 gotcha: the archive step writes to
pgserve-v2's `pgserve_meta` (deleted in v3) → migration aborts after
already renaming the source DB. Workaround: `--no-archive` on v3;
recover from a half-archive by renaming `genie_archive_<date>` back to
`genie`. Verified on this host.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
docs(upgrade): post-upgrade work-recovery runbook + migrate-v1 v3 gotcha
@coderabbitai

coderabbitai Bot commented May 19, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Release v4.260519.3 bumps package versions, updates metrics state, embeds SQL migrations at build time via codegen, and adds pgserve v3 direct-postmaster database provisioning with validation and bootstrap helpers.

Changes

Release Bundle: Migration Embedding and DB Bootstrap

Layer / File(s) Summary
Version and metrics updates
.claude-plugin/marketplace.json, package.json, plugins/genie/.*
Release version bumped to 4.260519.3 across manifests and package files; metrics-updater state advanced with new 2026-05-19 entries for daily stats, run logs, and persisted counters.
SQL migration embedding codegen and types
scripts/gen-migrations-manifest.ts, src/db/migrations.generated.ts, src/types/sql.d.ts
New codegen script reads .sql migration files, validates basenames, generates manifest with static imports and EMBEDDED_MIGRATIONS array; TypeScript ambient module declares .sql file text imports.
Build script integration for codegen
scripts/build-binary.sh
Build script runs migration manifest codegen before compilation to ensure compiled binary includes current migration set.
Migration loading with embedded preference
src/lib/db-migrations.ts
loadMigrationFiles() prefers embedded migrations when available; falls back to on-disk scanning for development.
pgserve v3 direct-postmaster and database bootstrap
src/lib/db.ts
Database provisioning reworked: adds NATIVE_DB_NAME, ensureDatabaseExists() bootstrap helper, parsePostmasterDiscovery() validation, dual v3/v2 discovery support in readPostmasterDiscovery(), and hasV3RuntimeDiscovery() detection helper; connection pool setup resolves target database up front and provisions or overrides routing when using v3 sockets.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: a rolling promotion from dev to main that includes a native pgserve-v3 fix and recovery documentation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request implements support for pgserve v3 (autopg) by introducing native database provisioning and a new mechanism to embed SQL migrations directly into the compiled binary. It includes a migration manifest generation script, logic to detect v3 runtime environments, and an upgrade guide. Feedback identifies performance optimization opportunities, specifically gating database existence checks for short-lived CLI commands and removing a redundant sort operation in the migration loader.

Comment thread src/lib/db.ts
Comment on lines +1352 to +1354
if (v3NativeDb && database !== DB_NAME) {
await ensureDatabaseExists(pgModule, transport, database);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The ensureDatabaseExists call is performed on every connection build when v3NativeDb is true. This includes short-lived CLI commands like genie hook dispatch (which sets GENIE_SKIP_DB_BOOT=1). Connecting to the maintenance database and querying pg_database on every hook execution adds significant overhead and contributes to the CPU saturation issues mentioned in the PR context. Since hooks are only executed when the system is already active, the database should already exist. This check should be gated by !cliShortLived to align with the other performance optimizations for short-lived commands.

  if (v3NativeDb && database !== DB_NAME && !cliShortLived) {
    await ensureDatabaseExists(pgModule, transport, database);
  }

Comment thread src/lib/db-migrations.ts
Comment on lines +74 to +76
return EMBEDDED_MIGRATIONS.map((m) => ({ name: m.name, sql: m.sql })).sort((a, b) =>
a.name < b.name ? -1 : a.name > b.name ? 1 : 0,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The sort() call here is redundant. The scripts/gen-migrations-manifest.ts script already sorts the migration files lexicographically before generating the EMBEDDED_MIGRATIONS array. Removing this extra sort will slightly improve the performance of connection establishment.

    return EMBEDDED_MIGRATIONS.map((m) => ({ name: m.name, sql: m.sql }));

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2328cd360c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/db-migrations.ts
Comment on lines +73 to +77
if (EMBEDDED_MIGRATIONS.length > 0) {
return EMBEDDED_MIGRATIONS.map((m) => ({ name: m.name, sql: m.sql })).sort((a, b) =>
a.name < b.name ? -1 : a.name > b.name ? 1 : 0,
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Prefer on-disk migrations in source runs

loadMigrationFiles() now returns EMBEDDED_MIGRATIONS whenever it is non-empty, which makes the filesystem scan unreachable in normal source checkouts because src/db/migrations.generated.ts is committed and populated. That means adding a new src/db/migrations/*.sql file without regenerating the manifest will silently skip that migration in genie db migrate (schema drift until runtime failures on new columns/tables). This regression is introduced here because previously the loader always read the directory at runtime; consider gating embedded-only behavior to compiled binaries or validating manifest/file parity before returning.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/db.ts`:
- Around line 233-238: The discovered socket check should verify the postmaster
is actually responsive rather than just the pathname existing: after computing
socketDir, port and pid (the block that returns { socketDir, port, pid: pid ??
-1 }), attempt a liveness probe before returning — e.g., if pid is present use
process.kill(pid, 0) to check the process exists (catch ESRCH and other errors),
and/or attempt a short timed Unix domain socket connect to join(socketDir,
`.s.PGSQL.${port}`) using net.Socket to confirm it accepts connections; only
return the discovery object when one of these probes succeeds, otherwise treat
it as stale and return null. Apply the same probe logic to the similar discovery
path in the later block covering lines 255-296 so both discovery branches
validate liveness.
- Around line 90-97: The code returns the GENIE_DB_NAME override unconditionally
(variable explicit) before checking runtime discovery mode, which allows v1/v2
paths to incorrectly use a named DB; update the logic so you only honor
process.env.GENIE_DB_NAME when hasV3RuntimeDiscovery() is true (e.g., call
hasV3RuntimeDiscovery() and return explicit only if true and explicit is
non-empty), and apply the same gated check to the other identical override block
later in the file (the second explicit/GENIE_DB_NAME handling).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 952cbc4c-5d27-44b3-aea0-26477bba1f64

📥 Commits

Reviewing files that changed from the base of the PR and between 2d95bf3 and 2328cd3.

⛔ Files ignored due to path filters (6)
  • .genie/assets/commits-30d.svg is excluded by !**/*.svg
  • .genie/assets/loc-30d.svg is excluded by !**/*.svg
  • .genie/assets/releases-30d.svg is excluded by !**/*.svg
  • README.md is excluded by !*.md
  • UPGRADING-pgserve-v3.md is excluded by !*.md
  • VELOCITY.md is excluded by !*.md
📒 Files selected for processing (13)
  • .claude-plugin/marketplace.json
  • .genie/agents/metrics-updater/daily-stats.jsonl
  • .genie/agents/metrics-updater/runs.jsonl
  • .genie/agents/metrics-updater/state.json
  • package.json
  • plugins/genie/.claude-plugin/plugin.json
  • plugins/genie/package.json
  • scripts/build-binary.sh
  • scripts/gen-migrations-manifest.ts
  • src/db/migrations.generated.ts
  • src/lib/db-migrations.ts
  • src/lib/db.ts
  • src/types/sql.d.ts

Comment thread src/lib/db.ts
Comment on lines +90 to +97
// Non-test explicit override. On a direct postmaster (autopg/pgserve v3,
// no accept-hook router) there is nothing to silently re-route the libpq
// default `postgres` into a per-fingerprint DB, so genie must target a
// real named database. `GENIE_DB_NAME` lets operators pin it; the
// direct-postmaster bootstrap defaults it to NATIVE_DB_NAME and creates
// it if absent. Legacy router path keeps returning DB_NAME.
const explicit = process.env.GENIE_DB_NAME;
if (explicit && explicit.length > 0) return explicit;

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 | ⚡ Quick win

Gate GENIE_DB_NAME to v3-only paths.

This override is resolved before you've established hasV3RuntimeDiscovery(), so v1/TCP and v2/router connections can still leave postgres and run bootstrap/migrations against a fresh named DB. That breaks the back-compat contract this PR is trying to preserve.

Suggested fix
 export function resolveDatabaseName(): string {
   const testDbName = process.env.GENIE_TEST_DB_NAME;
   if (testDbName && testDbName.length > 0) return testDbName;
-  // Non-test explicit override. On a direct postmaster (autopg/pgserve v3,
-  // no accept-hook router) there is nothing to silently re-route the libpq
-  // default `postgres` into a per-fingerprint DB, so genie must target a
-  // real named database. `GENIE_DB_NAME` lets operators pin it; the
-  // direct-postmaster bootstrap defaults it to NATIVE_DB_NAME and creates
-  // it if absent. Legacy router path keeps returning DB_NAME.
-  const explicit = process.env.GENIE_DB_NAME;
-  if (explicit && explicit.length > 0) return explicit;
   return DB_NAME;
 }
@@
-  if (v3NativeDb && !isTestMode && database === DB_NAME) {
-    database = NATIVE_DB_NAME;
-  }
+  if (v3NativeDb && !isTestMode) {
+    const explicit = process.env.GENIE_DB_NAME;
+    if (explicit && explicit.length > 0) {
+      database = explicit;
+    } else if (database === DB_NAME) {
+      database = NATIVE_DB_NAME;
+    }
+  }
   if (v3NativeDb && database !== DB_NAME) {
     await ensureDatabaseExists(pgModule, transport, database);
   }

Also applies to: 1314-1354

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/db.ts` around lines 90 - 97, The code returns the GENIE_DB_NAME
override unconditionally (variable explicit) before checking runtime discovery
mode, which allows v1/v2 paths to incorrectly use a named DB; update the logic
so you only honor process.env.GENIE_DB_NAME when hasV3RuntimeDiscovery() is true
(e.g., call hasV3RuntimeDiscovery() and return explicit only if true and
explicit is non-empty), and apply the same gated check to the other identical
override block later in the file (the second explicit/GENIE_DB_NAME handling).

Comment thread src/lib/db.ts
Comment on lines 233 to 238
if (!socketDir || !port) return null;
// Validate the postmaster socket file exists. If pgserve crashed without
// cleaning up admin.json, the file path will be stale.
// Validate the postmaster socket file exists. If the server crashed
// without cleaning the discovery file, the path will be stale.
if (!existsSync(join(socketDir, `.s.PGSQL.${port}`))) return null;
// pid is informational — we don't gate on liveness here because the daemon
// process supervises the postmaster lifecycle. Stale pid + present socket
// is impossible under normal operation.
// pid is informational — the supervisor owns postmaster lifecycle.
return { socketDir, port, pid: pid ?? -1 };

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 | 🏗️ Heavy lift

Validate discovered sockets for liveness, not just path existence.

A leftover .s.PGSQL.<port> pathname still satisfies existsSync(), so runtime.json/admin.json can be treated as live after an unclean crash. In that case the client will prefer a dead direct socket and miss the healthy fallback path. Please probe the discovered socket or otherwise verify the postmaster is still alive before returning discovery.

Also applies to: 255-296

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/db.ts` around lines 233 - 238, The discovered socket check should
verify the postmaster is actually responsive rather than just the pathname
existing: after computing socketDir, port and pid (the block that returns {
socketDir, port, pid: pid ?? -1 }), attempt a liveness probe before returning —
e.g., if pid is present use process.kill(pid, 0) to check the process exists
(catch ESRCH and other errors), and/or attempt a short timed Unix domain socket
connect to join(socketDir, `.s.PGSQL.${port}`) using net.Socket to confirm it
accepts connections; only return the discovery object when one of these probes
succeeds, otherwise treat it as stale and return null. Apply the same probe
logic to the similar discovery path in the later block covering lines 255-296 so
both discovery branches validate liveness.

@namastex888
namastex888 merged commit bca0067 into main May 20, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants