Skip to content

[Bug]: Worktree pairing can migrate the live desktop database #10516

Closed

Description

@chrisdeeming

Before submitting

  • I searched existing issues and did not find a duplicate of this specific failure.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. Keep the installed desktop server running.
  2. In a development worktree with a pending migration, run node apps/server/src/bin.ts pair without --base-dir, with no discoverable worktree server.
  3. Discovery falls back to the installed server; token creation opens its database using the development checkout's migrations.

Expected behavior

Worktree pairing must not silently target the live install. Minting a token should not run a different checkout's schema migrations.

Actual behavior

An agent trying to pair a dev browser migrated my live database. On September 3, the command returned the installed server on port 3773. The live DB recorded migration 48 (ProjectionThreadMessageContext, unmerged work) and the t3 pair token in the same second: 16:33:46 UTC.

Updating later broke both installed builds: released migration 48 is ProjectionThreadBranchPullRequest, so it was skipped and startup failed on the missing column.

Code path: cli/pair.ts shared-home fallback → EnvironmentAuth.runtimeLayer → SQLite setup → runMigrations().

Related: #8896 covers migration identity validation; this report concerns the pairing command that unexpectedly applies development migrations to live data.

Impact

Blocks work completely

Version or commit

Affected installed builds: 0.0.39 and 0.0.40-nightly.20260907.1346; pairing ran from an unmerged inline-context worktree.

Environment

macOS, Electron desktop; Node CLI invoked from a linked git worktree.

Logs or stack traces

Pairing with MacBook Pro (http://127.0.0.1:3773).
PersistenceSqlError: ProjectionSnapshotQuery.getCommandReadModel:listThreads:query
Error: no such column: branch_pull_request_json

Activity

  1. juliusmarminge commented on Sep 7, 2026

    @juliusmarminge
    Member

    Triage

    Confirmed on current main. This is a real, data-destructive pairing bug, not a duplicate of #8896.

    What we verified

    t3 pair without --base-dir discovers a live server, then mints a token by opening that server’s SQLite file in-process.

    1. Discovery falls through to the install. In apps/server/src/cli/pair.ts, discoverPairTarget checks the worktree .t3 first, then always appends the shared T3 home (T3CODE_HOME / ~/.t3). The first live server-runtime.json + pid + /.well-known/t3/environment hit wins. If no worktree server is running, that is the installed desktop (typically port 3773).

      The file comment says this matches dev-runner precedence. It does not. scripts/dev-runner.ts is exclusive (worktreeHome ?? shared home). Pairing is inclusive and silently retargets the daily-driver DB.

    2. Minting applies this checkout’s migrations. mintPairingLink provides EnvironmentAuth.runtimeLayer, which includes SqlitePersistenceLayer. apps/server/src/persistence/Layers/Sqlite.ts calls runMigrations() on every open. Those migrations come from the CLI checkout, not from the running install.

    That matches the report: worktree pair → “Pairing with MacBook Pro (http://127.0.0.1:3773)” → live journal wrote unmerged 48_ProjectionThreadMessageContext in the same second as the t3 pair token. Released 048 is ProjectionThreadBranchPullRequest (branch_pull_request_json). Effect’s migrator then skipped it by ID, and both 0.0.39 and 0.0.40-nightly.20260907.1346 failed on the missing column.

    apps/server/src/cli/pair.test.ts covers explicit --base-dir success/failure and stale pid/state. It does not cover “linked worktree, no local server, shared home must not be used.”

    Related, not duplicates

    Suggested fix

    1. Fail closed in a worktree. If resolveWorktreeT3Home(cwd) is set and that home has no live server, raise NoRunningServerError. Only target the shared install when --base-dir is explicit (or the cwd is not a linked worktree). Add a regression test for that fall-through.
    2. Do not migrate a live install from another checkout. Pairing should ask the already-running server to mint the token, or refuse if effect_sql_migrations names do not match this checkout. Opening the install DB through runtimeLayer is unsafe even if discovery is later made explicit.

    migrate-dev-db.ts already refuses slot collisions. Pairing should be at least that careful, and should not touch the install DB from a worktree by default.

    Workaround / recovery

    Next step

    Implement the fail-closed discovery change and the regression first; that stops the live-DB write. Treat “mint without this checkout’s runMigrations()” as the follow-up so auth pairing create cannot do the same thing with an explicit --base-dir.

  2. added
    via-triageFiled through npx t3 triage
    bugSomething is broken or behaving incorrectly.
    on Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions