Skip to content

fix(db): drop the premature event_order index that crash-loops Postgres upgrades - #2513

Open
133Felix wants to merge 1 commit into
coleam00:devfrom
133Felix:fix/2508-pg-event-order-index-ordering
Open

fix(db): drop the premature event_order index that crash-loops Postgres upgrades#2513
133Felix wants to merge 1 commit into
coleam00:devfrom
133Felix:fix/2508-pg-event-order-index-ordering

Conversation

@133Felix

@133Felix 133Felix commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem: On PostgreSQL, applying migrations/000_combined.sql against an existing database aborts with ERROR: 42703: column "event_order" does not exist. idx_workflow_events_run_order is created in the CREATE TABLE IF NOT EXISTS remote_agent_workflow_events section (line 285), ~265 lines before the ALTER TABLE ... ADD COLUMN IF NOT EXISTS event_order that creates the column it indexes (line 549).
  • Why it matters: initSchema() wraps the whole file in one transaction and re-throws fatal, so the apply rolls back and the server crash-loops — every Postgres install predating feat: summarize verbose workflow JSON nodes #2414 fails to start after upgrading, and every CLI invocation fails too (both apply schema on connect). Fresh installs are unaffected, because the real CREATE TABLE declares event_order inline — which is exactly why this shipped green.
  • What changed: Deleted the premature CREATE UNIQUE INDEX. The identical statement in the additive block (line 556) already creates it directly after the ADD COLUMN, which is correct for both a fresh and an upgrading database. Added a statement-ordering regression test that reads the SQL and asserts on offsets.
  • What did not change (scope boundary): The SQLite path (already fixed by fix(db): SQLite upgrade broke on event_order — index/trigger ran before the ALTER #2418), the inline event_order BIGINT in the CREATE TABLE body (kept — it is what makes fresh installs correct), and the two sibling instances of this same bug class (idx_conversations_hidden, idx_sessions_parent) which are already covered by the open fix(migrations): create indexes after ADD COLUMN for existing Postgres DBs #2443.

UX Journey

Before

  Operator                Archon (Postgres adapter)          PostgreSQL
  ────────                ─────────────────────────          ──────────
  upgrade + restart ────▶ initSchema(): BEGIN
                          apply 000_combined.sql ──────────▶ CREATE TABLE IF NOT EXISTS
                                                             (no-op: table exists,
                                                              event_order NOT added)
                                                   ────────▶ line 285: CREATE UNIQUE INDEX
                                                              ...(workflow_run_id, event_order)
                          [X] fatal re-throw      ◀───────── ERROR 42703
                          ROLLBACK
  server never starts ◀── crash, restart, repeat ◀────────── (nothing committed)

After

  Operator                Archon (Postgres adapter)          PostgreSQL
  ────────                ─────────────────────────          ──────────
  upgrade + restart ────▶ initSchema(): BEGIN
                          apply 000_combined.sql ──────────▶ CREATE TABLE IF NOT EXISTS
                                                             (no-op)
                                                   ────────▶ *line 285: index NOT created here*
                                                   ────────▶ ALTER TABLE ADD COLUMN
                                                              IF NOT EXISTS event_order  [+]
                                                   ────────▶ CREATE SEQUENCE / SET DEFAULT
                                                   ────────▶ CREATE UNIQUE INDEX
                                                              ...(workflow_run_id, event_order)
                          COMMIT                  ◀───────── ok
  server starts ◀──────── healthy

Fresh installs take the same path and are unchanged: CREATE TABLE declares event_order inline, the ADD COLUMN is a no-op, and the single index creation succeeds.

Architecture Diagram

Before

  migrations/000_combined.sql
    ├── [Table 6 block]  CREATE TABLE IF NOT EXISTS remote_agent_workflow_events
    │                      (event_order BIGINT declared inline)
    │                    CREATE UNIQUE INDEX idx_workflow_events_run_order  ◀── premature
    │
    └── [additive block] ALTER TABLE ... ADD COLUMN IF NOT EXISTS event_order
                         CREATE SEQUENCE ... OWNED BY ...event_order
                         ALTER COLUMN event_order SET DEFAULT nextval(...)
                         CREATE UNIQUE INDEX idx_workflow_events_run_order  ◀── duplicate

  packages/core/src/db/bundled-schema.generated.ts   (generated from the above)
  packages/core/src/db/adapters/postgres.ts          (initSchema: BEGIN / apply / COMMIT)

After

  migrations/000_combined.sql                                                   [~]
    ├── [Table 6 block]  CREATE TABLE IF NOT EXISTS remote_agent_workflow_events
    │                      (event_order BIGINT declared inline)
    │                    -- comment explaining why the index is NOT here        [+]
    │
    └── [additive block] ALTER TABLE ... ADD COLUMN IF NOT EXISTS event_order
                         CREATE SEQUENCE ... OWNED BY ...event_order
                         ALTER COLUMN event_order SET DEFAULT nextval(...)
                         CREATE UNIQUE INDEX idx_workflow_events_run_order      ◀── sole copy

  packages/core/src/db/bundled-schema.generated.ts                              [~] regenerated
  packages/core/src/db/migration-statement-order.test.ts                        [+] new guard
  packages/core/src/db/adapters/postgres.ts                                         unchanged

Connection inventory

From To Status Notes
migrations/000_combined.sql bundled-schema.generated.ts unchanged Regenerated via bun run generate:bundled-schema; check:bundled-schema green
postgres.ts initSchema() migrations/000_combined.sql unchanged Same single-transaction apply; only the SQL content changed
migration-statement-order.test.ts migrations/000_combined.sql new Reads the file and asserts statement offsets; no mocks, no database
sqlite.ts createSchema() migrations/000_combined.sql unchanged SQLite maintains its schema separately; unaffected (#2418 fixed that path)

Label Snapshot

  • Risk: risk: low
  • Size: size: S
  • Scope: core
  • Module: core:db

Change Metadata

  • Change type: bug
  • Primary scope: core

Linked Issue

Validation Evidence (required)

bun run validate     # EXIT=0 — all nine checks green
  • Evidence provided:
    • bun run validate exits 0: check:bundled, check:bundled-skill, check:bundled-schema, check:pi-vendor-map, check:capability-matrix, type-check, lint --max-warnings 0, format:check, test:install, test.
    • New test red-green verified. With the fix reverted (git stash push -- migrations/000_combined.sql):
      (fail) migrations/000_combined.sql — statement ordering > event_order is added before anything indexes it (#2508)
       2 pass
       1 fail
      
      With the fix applied: 3 pass, 0 fail.
    • Reproduced and fixed against a real, live Postgres database — see Human Verification.
  • Skipped commands: none.

Security Impact (required)

  • New permissions/capabilities? No
  • New external network calls? No
  • Secrets/tokens handling changed? No
  • File system access scope changed? No — the new test reads migrations/000_combined.sql from the repo, the same file getSchemaSQL() already reads in source builds.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Database migration needed? No — this repairs the migration itself. Fresh installs are byte-identical in outcome (same column, same sequence, same index, same default); upgrades now converge instead of aborting.
  • For anyone already stuck in the crash-loop, this PR fixes the boot going forward. The manual unblock is the same statement the migration performs:
    ALTER TABLE remote_agent_workflow_events ADD COLUMN IF NOT EXISTS event_order BIGINT;

Human Verification (required)

  • Verified scenarios:
    • Hit this in production while upgrading a self-hosted install from 0.6.0 to 0.8.0 against PostgreSQL. The container crash-looped with db.postgres_schema_init_faileddatabase_connection_failed → exit 1, repeating indefinitely; /api/health never answered. Log:
      {"level":60,"module":"db.postgres","err":{"type":"DatabaseError",
       "message":"column \"event_order\" does not exist","code":"42703",
       "position":"12720"},"msg":"db.postgres_schema_init_failed"}
      
      Byte offset 12720 lands exactly on the premature index this PR removes.
    • Applying the ADD COLUMN by hand and restarting brought the app up immediately (healthy after ~6s), which isolates this statement as the sole cause.
    • After the restart the rest of the schema applied correctly: output_root present, event_order carrying DEFAULT nextval(...), idx_workflow_events_run_order present, remote_agent_schema_version created (18 → 19 tables).
  • Edge cases checked:
    • 23,221 pre-existing event rows keep event_order IS NULL — no backfill, exactly as the migration comment intends, and what the COALESCE(event_order, 0) read path expects.
    • Fresh-install path unchanged: the CREATE TABLE body still declares event_order, asserted by the third test so a later cleanup cannot remove it and silently break new installs.
    • Both the sequence's OWNED BY and the ALTER COLUMN ... SET DEFAULT are asserted to follow the ADD COLUMN too — they fail with the same 42703 if reordered.
  • What was not verified:
    • I did not re-run a clean-database bootstrap from an empty Postgres in CI; the fresh path is covered by reasoning (the CREATE TABLE body is unchanged) plus the third test, not by an end-to-end fresh apply.
    • I did not touch or verify the two sibling instances in fix(migrations): create indexes after ADD COLUMN for existing Postgres DBs #2443. They are the same bug class but latent for my upgrade, because hidden and parent_session_id already existed in my database.

Side Effects / Blast Radius (required)

  • Affected subsystems/workflows: Postgres schema apply at boot (initSchema()), which every server start and every CLI invocation runs. Nothing at runtime — no query, no read path, no API surface changes.
  • Potential unintended effects: If some environment somehow relied on the index existing before the additive block, it would now be created slightly later within the same transaction. Since the whole file applies atomically, nothing outside the transaction can observe the difference.
  • Guardrails/monitoring: db.postgres_schema_init_failed is already logged at fatal with the SQLSTATE and byte position, which is what made this diagnosable in minutes. The new test fails at CI time if any statement referencing event_order migrates back above the ADD COLUMN.

Rollback Plan (required)

  • Fast rollback: git revert <sha> — the change is one deleted SQL statement plus a test. Reverting restores the previous (broken-for-upgrades) behaviour, so it should only be done if the deletion turns out to break fresh installs, which the third test guards against.
  • Feature flags / toggles: none.
  • Observable failure symptoms: any column "..." does not exist at boot in db.postgres_schema_init_failed, or idx_workflow_events_run_order missing after a successful apply (\d remote_agent_workflow_events).

Risks and Mitigations

Summary by CodeRabbit

  • Bug Fixes

    • Improved database migration compatibility for both fresh installations and existing databases by ensuring workflow event indexes are created only after the required column is added.
  • Tests

    • Added validation to confirm migration statements execute in the correct order and preserve the expected schema for new installations.

…es upgrades

`initSchema()` applies `migrations/000_combined.sql` top-to-bottom inside a
single transaction and re-throws fatally, so one statement referencing a column
that does not exist yet rolls the whole apply back and crash-loops the boot.

`idx_workflow_events_run_order` was created twice: once inside the
`CREATE TABLE IF NOT EXISTS remote_agent_workflow_events` section, and again in
the additive block directly after the `ALTER TABLE ... ADD COLUMN IF NOT EXISTS
event_order` that creates the column it indexes.

On a FRESH database the first one works — `CREATE TABLE` runs for real and
declares `event_order` inline. On an EXISTING database `CREATE TABLE IF NOT
EXISTS` is a no-op, so the column is absent ~265 lines before the ALTER that
adds it, and the index aborts with:

    ERROR: 42703: column "event_order" does not exist

That asymmetry is why this shipped green: only upgrades are affected, and only
those from a database predating coleam00#2414.

The fix is to delete the premature copy. The one in the additive block is
correct for both cases — it runs on every apply, after the column is guaranteed
to exist — so nothing is lost for fresh installs.

Adds a statement-ordering guard that reads the SQL and asserts every reference
to `event_order` (the unique index, the sequence's OWNED BY, and the ALTER
COLUMN ... SET DEFAULT) follows the ADD COLUMN, and that the CREATE TABLE body
still declares the column so fresh installs stay correct. No mocks, no database.

coleam00#2418 applied the same reordering to the SQLite path; this is the Postgres one.
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The migration defers the workflow-events index until after event_order is added. A new test inspects SQL statement order and confirms the fresh-installation declaration. The core test script runs this validation with the database tests.

Changes

Migration ordering

Layer / File(s) Summary
Defer dependent index creation
migrations/000_combined.sql
The workflow-events index is created after the additive event_order column change.
Validate migration statement order
packages/core/src/db/migration-statement-order.test.ts, packages/core/package.json
Tests verify dependent statements follow the column addition. The core test script runs the new suite.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

Possibly related PRs

Suggested labels: bug, area: database, P0

Suggested reviewers: wirasm

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the premature event_order index and the PostgreSQL upgrade crash-loop it fixes.
Description check ✅ Passed The description follows the repository template and provides complete scope, validation, compatibility, verification, risk, and rollback details.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 install failed: dependency version conflict. Check your lock file or package.json.


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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 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 `@packages/core/src/db/migration-statement-order.test.ts`:
- Around line 34-36: Guard the match offset in the indexRef loop before adding
it to indexOffsets, skipping or otherwise handling matches where m.index is
undefined. Ensure only numeric offsets are pushed into the number[] while
preserving the existing CREATE UNIQUE INDEX matching behavior.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0bc70e37-21ef-4334-8115-f1a251a27540

📥 Commits

Reviewing files that changed from the base of the PR and between 41765d6 and baa9376.

⛔ Files ignored due to path filters (1)
  • packages/core/src/db/bundled-schema.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (3)
  • migrations/000_combined.sql
  • packages/core/package.json
  • packages/core/src/db/migration-statement-order.test.ts

Comment on lines +34 to +36
const indexOffsets: number[] = [];
const indexRef = /CREATE\s+UNIQUE\s+INDEX[^;]*idx_workflow_events_run_order/gi;
for (const m of SCHEMA_SQL.matchAll(indexRef)) indexOffsets.push(m.index);

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
git ls-files | grep -F 'packages/core/src/db/migration-statement-order.test.ts' || true

echo "== package tsconfig options =="
fd -a 'tsconfig.*json' . | sed 's#^\./##' | head -50
for f in $(fd 'tsconfig.*json' .); do
  echo "--- $f"
  jq '{compilerOptions?: .compilerOptions}' "$f" 2>/dev/null | head -40
done

echo "== relevant test lines =="
cat -n packages/core/src/db/migration-statement-order.test.ts | sed -n '1,90p'

echo "== TypeScript availability/version =="
if command -v node >/dev/null 2>&1; then node --version; fi
if command -v bun >/dev/null 2>&1; then bun --version; fi
if command -v tsc >/dev/null 2>&1; then tsc --version; fi

echo "== local TS lib declaration excerpt if available =="
node - <<'JS'
try {
  const libPath = require.resolve('typescript/lib/lib.es5.d.ts');
  const fs = require('fs');
  const text = fs.readFileSync(libPath, 'utf8');
  const matches = [...text.matchAll(/interface\s+RegExpMatchArray[\s\S]*?^}/m)];
  console.log('found lib:', libPath);
  for (const m of matches) console.log(m[0]);
} catch (e) {
  console.log('typescript lib not resolvable:', e.message);
}
JS

Repository: coleam00/Archon

Length of output: 909


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package/core tsconfig =="
cat -n packages/core/tsconfig.json

echo "== jsconfig/tslint/eslint rules if present =="
for f in tsconfig.json jsconfig.json tslint.json eslint.config.js eslint.config.mjs eslint.config.cjs; do
  if [ -f "$f" ]; then echo "--- $f"; sed -n '1,240p' "$f"; fi
done

echo "== relevant test lines =="
cat -n packages/core/src/db/migration-statement-order.test.ts | sed -n '1,100p'

echo "== RegExpMatchArray declaration excerpt =="
node - <<'JS'
try {
  const libPath = require.resolve('typescript/lib/lib.es5.d.ts');
  const fs = require('fs');
  const text = fs.readFileSync(libPath, 'utf8');
  const matches = [...text.matchAll(/interface\s+RegExpMatchArray[\s\S]*?^}/m)];
  console.log('found lib:', libPath);
  for (const m of matches) {
    console.log(m[0]);
    break;
  }
} catch (e) {
  console.log('typescript lib not resolvable:', e.message);
}
try {
  const ts = require('typescript');
  console.log('typescript version:', ts.version);
} catch (e) {
  console.log('typescript package not resolvable:', e.message);
}
JS

Repository: coleam00/Archon

Length of output: 10557


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package manifests mentioning typescript/bun/types =="
for f in package.json bun.lockb bun.lock pnpm-lock.yaml yarn.lock; do
  if [ -f "$f" ]; then
    echo "--- $f"
    case "$f" in
      package.json) jq '{dependencies, devDependencies}' "$f" ;;
      bun.lockb|bun.lock|pnpm-lock.yaml|yarn.lock) sed -n '1,220p' "$f" ;;
    esac
  fi
done

echo "== check installed bun-types declarations for RegExpMatchArray (if available) =="
node - <<'JS'
const paths = [
  process.env.NODE_PATH,
  '/usr/local/lib/node_modules/bun-types/global.d.ts',
  '/usr/local/lib/node_modules/@types/bun/global.d.ts',
  '/usr/local/lib/node_modules/@types/bun/index.d.ts',
].filter(Boolean);
for (const p of paths) {
  try {
    const fs = require('fs');
    const text = fs.readFileSync(p, 'utf8');
    console.log('FOUND:', p);
    const idx = text.indexOf('interface RegExpMatchArray');
    if (idx === -1) {
      console.log(' no RegExpMatchArray');
    } else {
      console.log(text.slice(idx, text.indexOf('\n}', idx) + 3));
    }
  } catch (e) {}
}
try {
  // Read from any node_modules available, if present.
  const fs = require('fs');
  const base = require('path').join(__dirname, 'node_modules', '`@types`', 'bun', 'global.d.ts');
  const text = fs.readFileSync(base, 'utf8');
  console.log('FOUND-local:', base);
  console.log(text.includes('interface RegExpMatchArray') ? 'contains' : 'missing');
} catch (e) {}
JS

echo "== run package check for TypeScript type errors =="
if command -v bun >/dev/null 2>&1; then
  bun run --filter `@archon/core` check
fi

Repository: coleam00/Archon

Length of output: 7547


🌐 Web query:

TypeScript RegExpMatchArray index optional official types

💡 Result:

In TypeScript, the index and input properties on the RegExpMatchArray type are defined as optional (e.g., index?: number, input?: string) [1]. This design reflects the runtime behavior of the String.prototype.match method, which returns these properties only when the regular expression does not use the global (g) flag [2][1][3]. When the global flag is used, String.prototype.match returns an array of matched strings without the additional properties such as index and input, which is why TypeScript marks them as optional in the type definition [2][1][3]. If you are working in an environment where you know these properties will be present (i.e., you are not using the global flag), you may need to use a type assertion or a non-null assertion operator (e.g., match!.index) to inform the TypeScript compiler that the values are defined [4]. Alternatively, if you need guaranteed index and input properties, you might consider using RegExp.prototype.exec instead, which returns a RegExpExecArray, where these properties are traditionally treated as present [5][1].

Citations:


Handle the optional match offset before pushing it.

RegExpMatchArray.index is optional, so indexOffsets.push(m.index) passes number | undefined to number[]. Add a guard for m.index === undefined before pushing.

🤖 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 `@packages/core/src/db/migration-statement-order.test.ts` around lines 34 - 36,
Guard the match offset in the indexRef loop before adding it to indexOffsets,
skipping or otherwise handling matches where m.index is undefined. Ensure only
numeric offsets are pushed into the number[] while preserving the existing
CREATE UNIQUE INDEX matching behavior.

Source: Coding guidelines

@tazmon95

tazmon95 commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Independent verification of this fix, from a production install that hit #2508.

Real-world impact first: this bug took our Archon deployment down for ~13 minutes on 2026-08-09. We bumped 0.5.0 → 0.8.0 having passed workflow validation, our full contract suite, unit tests at 100% coverage, runtime gate probes and green CI — every one of which runs against a fresh database. The upgrade crash-looped immediately on our existing Postgres and we rolled back. #2508's own line about why it shipped green is exactly why our checks were blind to it too:

"Fresh installs are unaffected, because the real CREATE TABLE declares event_order inline — which is exactly why this shipped green."

Verification method. Schema-only pg_dump of the affected production database (18 tables, pre-#2414 — no event_order column), restored into a throwaway postgres:16, then migrations/000_combined.sql applied with psql -1 -v ON_ERROR_STOP=1 to mirror initSchema()'s single-transaction + fatal-rethrow behaviour.

Results — three paths, same fixture:

Path 000_combined.sql Result
Upgrade (pre-#2414 schema) v0.8.0 ERROR: column "event_order" does not exist at line 287
Upgrade (pre-#2414 schema) this PR ✅ exit 0
Fresh install (empty DB) this PR ✅ exit 0
Re-apply ×2 (every boot) this PR ✅ exit 0, exit 0

Post-state assertions on the upgrade path with this PR applied:

  • event_order column present ✅
  • idx_workflow_events_run_order present ✅ — the index is still created, just in the additive block after the ALTER TABLE, so deleting the premature statement does not lose it
  • Fresh install ends in the same state (column + index) ✅
  • Idempotent: 2nd and 3rd apply both exit 0 with the index retained ✅

I checked the index specifically because "delete a CREATE INDEX" is the kind of fix that can quietly trade a crash for a missing index. It doesn't here — both paths converge on the same schema.

Rollback behaviour confirmed too: after the failing v0.8.0 apply, event_order is still absent, i.e. the transaction rolls back cleanly and leaves nothing partially migrated. That matches #2508's claim and is what made our rollback safe.

This fix resolves the failure for us. We've pinned to 0.7.0 in the meantime, since 0.7.1 and 0.8.0 both reproduce it against a pre-#2414 database.

One suggestion, offered rather than asked for: the reason this class escapes CI is that every migration test starts from an empty database. A CI job that applies 000_combined.sql to a schema snapshot from an older release tag before applying the current one would catch this and the two siblings in #2443. Happy to open that separately if it'd be useful.

@tazmon95

Copy link
Copy Markdown
Collaborator

Follow-up to my earlier SQL-level verification — I've now tested the branch itself, built and booted, rather than just the migration file.

End-to-end

Built Archon from baa93763 and booted it against a restored copy of the production Postgres that hit #2508 (schema-only dump, pre-#2414, no event_order column):

[verify-base] fixture restored: 18 tables
[verify-base] booting archon:2513-test against the restored production schema...
[verify-base] PASS — reached server_ready

For contrast, on the same fixture: ghcr.io/coleam00/archon:0.8.0 and :0.7.1 both fail with column "event_order" does not exist / 42703, and :0.7.0 passes. So the branch behaves like the last-good release rather than like the broken ones.

The shipped SQL in the built image is also correct — exactly one real statement, with the deletion documented in place:

285:-- idx_workflow_events_run_order is NOT created here (#2508). It indexes
558:CREATE UNIQUE INDEX IF NOT EXISTS idx_workflow_events_run_order

The new regression test earns its place

I checked it isn't a tautology by running it against v0.8.0's unfixed SQL — it fails, at the offset > addColumn assertion, which is precisely the defect. On the branch it passes (3 tests, 6 assertions).

It's also broader than the reported bug: it asserts every event_order reference follows the ADD COLUMN — the index, OWNED BY, and ALTER COLUMN ... SET DEFAULT — and separately that the inline event_order BIGINT stays in the CREATE TABLE body so fresh installs remain correct. That covers the failure class rather than the instance, which is what makes it worth keeping.

Other checks

  • check:bundled-schema — up to date, so the embedded schema didn't drift from the SQL.
  • @archon/core package test script (the batched invocation) — clean.
  • connection.test.ts isolated — 5 pass.

One note for anyone else verifying: running bun test packages/core/src/db/ in a single process reports 2 failures in connection.test.ts. Those are the mock.module pollution CLAUDE.md warns about, not branch regressions — they pass in isolation and under the package's own batched script. I nearly reported them as real.

No issues found. This resolves the crash-loop for us; we're pinned to 0.7.0 until a release carries it.

@tazmon95 tazmon95 closed this Aug 10, 2026
@tazmon95 tazmon95 reopened this Aug 11, 2026
@tazmon95

Copy link
Copy Markdown
Collaborator

@133Felix — apologies, this PR was closed by mistake and I've reopened it. It was not a rejection, and nothing about the fix prompted it.

What happened: we carry a downstream image that pins Archon as a base. Yesterday we shipped a temporary in-image patch of #2508 so we could run 0.8.0 before a release carries this fix. The commit message for that change contained the line:

Upstream fix archon#2513 is verified — we built Archon from that branch and ...

fix <ref> is a GitHub closing keyword, and archon#2513 resolved cross-repo. Merging our internal PR therefore auto-closed this one, at the same second, with no comment explaining it. Entirely my wording — I was crediting your work in a commit message and phrased it in a way that closed it instead.

I'm sorry. Having a correct, independently-verified fix closed silently by an unrelated downstream merge is precisely the kind of noise that makes contributing annoying, and you had no way to tell what had happened.

For the record, restating what I posted before the accidental close, since it now sits above it:

  • Built Archon from baa93763 and booted it against a restored copy of a production Postgres that hit Postgres schema apply crash-loops on upgrade: premature CREATE INDEX references event_order before ALTER TABLE adds it #2508reaches server_ready. The same fixture fails on 0.8.0 and 0.7.1 with column "event_order" does not exist / 42703, and passes on 0.7.0.
  • Applied transactionally as initSchema() does: exit 0, event_order present, idx_workflow_events_run_order present — the index still gets created, just after the ALTER TABLE, so removing the premature statement doesn't lose it.
  • Fresh install and repeated re-apply both clean.
  • The added regression test isn't a tautology: run it against 0.8.0's unfixed SQL and it fails at the offset > addColumn assertion. It also asserts the sequence and SET DEFAULT references, and that the inline event_order BIGINT stays in the CREATE TABLE body — so it covers the failure class rather than the one reported instance.

#2508 is still open and I don't see another PR addressing it, so as far as I can tell every Postgres install predating #2414 still can't upgrade. From our side this cost ~13 minutes of production downtime and we're now pinned behind a local patch until something lands.

I've changed our commit conventions so a downstream reference can't do this again.

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.

Postgres schema apply crash-loops on upgrade: premature CREATE INDEX references event_order before ALTER TABLE adds it

2 participants