Repository navigation
fix(server): legacy fork ledgers with title state at 52 split, and refused databases stop restarting - #139
Merged
Conversation
…s stop restarting Installs that ran a build recording upstream's ProjectionThreadTitleState at legacy slot 52 before fork main took over the ledger were not recognised as the fork's layout. The split skipped them, the V2 guard then refused the interleaved ledger, and the desktop app restarted the server forever. The split now recognises the fork's layout by any row carrying a fork name upstream never recorded at that id, accepts title state as a slot-52 alias, and keeps upstream names out of the fork ledger. Fixtures cover every slot 44-52 variant shipped agent builds recorded. A refused migration history now exits with SERVER_EXIT_CODE_DATABASE_REFUSED (65) before changing the database. The desktop app shows one error and quits, WSL backends stay stopped, and the service launcher idles instead of exiting into a systemd/launchd restart; an update trial still rolls back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On one install (build 400001,
cdbbed21bc), the one-time fork-ledger split from #133 didn't recognise the legacyeffect_sql_migrations, and T3 restart-looped. That ledger had 51 = AgentProfileAndPullRequestCompatibility and 52 = ProjectionThreadTitleState, followed by fork main's rows from 53 (RepairAgentUpgradeSchema, …, 78). The split only checked row 52 to decide whether a ledger was the fork's, so it left this one alone. The V2 guard then refused the mixed ledger (55_OrchestrationV2 (recorded: PullRequestFilesViewed) …), and the desktop app restarted the server every 10 s, indefinitely.dev-box split cleanly because its row 52 was
AgentProfileAndPullRequestCompatibilitybefore the split. Its fork row 1 still has that name and the 2026-09-15 timestamp, which is the day fork main first wrote 52. Its post-split 51/52 only look like the failing install's pre-split rows. No fork commit ever recorded TitleState at 52, so that row most likely came from an upstream-shaped build run on the same data around 14–16 Sep. This is inferred from the timestamps, not proven.Change
Refuse rather than start without splitting. If the ledger isn't split, the migrator skips every upstream id at or below the highest fork row, so the server would run with upstream schema missing. Refusing leaves the database untouched, and the error names the rows it refused. With exit 65, every supervisor now stops after the first refusal and stops retrying.
The
44:ProjectionThreadProfileSnapshot (this build: ClearAutomaticProjectModelDefaults)warning is accurate, so this PR leaves it in place. Upstream 44 is a one-time data cleanup with no schema change, and it never ran on that install. Running it now would be wrong: it relies on old-style project events, so it could clear a default model the user set after the V2 upgrade. The only lasting effect is that some older projects keep a default model that was set automatically, and the user can change it in project settings.The fork history since 2026-09-01 was walked commit by commit (
git log -p -- apps/server/src/persistence/Migrations.ts). Fork main only ever appended (the existing per-id tests cover 51–78). The other recorded variants come from agent builds before fork main. Each now has its own fixture.Scope and approval
Fixes a startup crash-loop that the fork's own migration repair (#133) caused on a real install. The direction (refuse with exit 65 and no loop, leave the 44 warning) was confirmed by the maintainer.
Verification
Before:
ForkMigrations.test.tswith the new fixture for the reported install fails at legacy ids 53, 54 and 78 with the samewould skip required schema migrations: 55_OrchestrationV2 (recorded: PullRequestFilesViewed) …that the install hit.After:
vp test run src/persistence/ForkMigrations.test.ts src/persistence/Sqlite.test.ts→ 58/58. The fixtures cover profile snapshots at 48/50, profile snapshots at 49/50, ListState at 52, the reported install (44/48/50/51 plus TitleState at 52), and the same install hand-relabelled at 52. Each runs at legacy ids 52, 53, 54 and 78, and each must give the same ledgers and the samesqlite_masterschema as a fresh database.Sqlite.test.tschecks that an unknown ledger fails startup withDatabaseRefusedErrorand exit code 65, and leaves the ledger unchanged.Desktop:
DesktopBackendManager.test.ts("stops without restarting when the server refuses its database"; one start after a minute on the TestClock) andDesktopWslBackend.test.ts. Server:serviceLauncher.test.ts(idles on 65; a trial that refuses rolls back and its database is restored).vp run check:changed: format, lint, knip, typecheck (30 packages) and the tests beside the changes all pass.Real data, read-only copy. The copy holds dev-box's live
statev2.sqliteschema (206 objects) and both migration ledgers. A script rewrites the copy into the single pre-split ledger and relabels it to the reported install's shape: 44/48/50ProjectionThreadProfileSnapshot, 51AgentProfileAndPullRequestCompatibility, 52ProjectionThreadTitleState, then fork main's 53–78. It also drops fork 11'sprofile_snapshotstable, which no pre-split database had. It then opens the copy throughSqlitePersistence.layerFromPath, the real startup layer.1a43bde71a):startup FAILED, exit code 1. The error isThis database's migration history would skip required schema migrations: 55_OrchestrationV2 (recorded: PullRequestFilesViewed), 56_RemoveRedundantProjectionIndexes (recorded: ScheduledTasks), …, and the ledger is unchanged. This is the error the field install hit, and the desktop app restarts on exit code 1 indefinitely.55e19a00f2): the log showsSplit the fork's migration history from upstream's { latestLegacyMigrationId: 78 }, thenMigrations ran successfully { migrations: [ 'fork/11_ProfileSnapshots' ] }, then the one accepted warning44:ProjectionThreadProfileSnapshot (this build: ClearAutomaticProjectModelDefaults). Startup succeeds. The ledgers end at upstream 1–68 and fork 1–11, the same result the hand-relabelled install got.SomeExperimentalMigration), after:startup FAILED, exit code 65. The error readsT3 Code did not open its database, and left it unchanged: … migrations it does not know: 60_SomeExperimentalMigration …. The ledger is unchanged, with no fork table created.Model and harness: Claude Opus 5.5 via Claude Code in T3 Code.
🤖 Generated with Claude Code