Skip to content

fix(cli): Keep the local database usable across CLI downgrades - #1612

Merged
BYK merged 6 commits into
mainfrom
fix/cli-db-newer-schema-downgrade
Oct 27, 2026
Merged

BYK merged 6 commits into
mainfrom
fix/cli-db-newer-schema-downgrade

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1611.

Running nightly migrates cli.db to schema 19. Going back to stable 0.47.0 (schema 16) then breaks in two ways, because older CLIs only migrate forward:

  • org_regions keeps the v17 composite key, so 0.47's ON CONFLICT(org_slug) upsert fails with "ON CONFLICT clause does not match…". Its auto-repair only checks composite keys, so it never fixes this.
  • v18 moved the login from auth into accounts. 0.47 recreates an empty auth table on startup, so the user is logged out without being told.

0.47.0 has already shipped, so the fix has to come from the newer CLI, which is the one running sentry cli upgrade stable:

  • Downgrade hand-off: when cli upgrade targets a release below 0.48.0, the first release that adapts a newer database by itself, it hands the database to the older CLI. It drops the cache tables (the older CLI recreates them in its own shape), deletes the stored accounts, and sets the schema version to 16. The older CLI can't read accounts, so the user runs sentry auth login there. This is simpler than keeping the old login format alive in newer CLIs.
  • When it runs: only after the older binary is downloaded, right before it runs cli setup. A failed download leaves the database and login untouched. It runs again after setup, even if setup fails, in case another invocation of the newer CLI re-migrated the database meanwhile. That second run does nothing unless the version is the running CLI's own, so it can't undo migrations the older CLI ran itself.
  • Upgrading back: the newer CLI re-runs migrations 17–19 from version 16 and moves any login made on the older CLI into accounts.
  • Future downgrades through other paths (npm/brew pinning): if the stored version is newer than the binary, the CLI rebuilds every cache table and records its own version. A newer CLI can change what cached rows mean, so they can't be trusted. With SENTRY_CLI_NO_AUTO_REPAIR=1, sentry cli fix reports the mismatch instead (CLI crashes with "ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint" after switching from nightly back to stable #1611 noted it used to print the binary's version and "No issues found").
  • Repair now compares every table's primary key using pragma_table_info, not just composite keys. Tables are marked rebuildable when they only hold derived data. Only those get dropped and recreated; a wrong key on any other table is reported and left alone.

Every migration after v16 now has to be safe to re-run. This is documented on prepareDatabaseForDowngrade.

The 0.48.0 threshold assumes this PR lands before 0.48.0 is released.

Backing up cli.db on upgrade and adding an undo command, which would keep the login across a downgrade, is tracked in #1623.

via BYK.

--

View Junior Session [Sentry]

@vercel

vercel Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
sentry-local Skipped Skipped Oct 27, 2026 5:20pm UTC

Request Review

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

Stale Bugbot comment from a previous run.

Comment thread packages/cli/src/commands/cli/upgrade.ts Outdated
Comment thread packages/cli/src/lib/db/schema.ts Outdated
@github-actions github-actions Bot added risk: high PR risk score: high risk: medium PR risk score: medium and removed risk: medium PR risk score: medium risk: high PR risk score: high labels Oct 23, 2026

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit dd06635. Configure here.

Comment thread packages/cli/src/commands/cli/upgrade.ts Outdated

@BYK BYK left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A cheaper alternative rather than trying to revert the db from code can be backing up the db along with the version the user is coming from and introducing a sentry cli revert or sentry cli upgrade undo or something like that which restores the previous db and database. This can be tracked in a separate issue while we do the resilience patches in this PR.

Comment thread packages/cli/src/lib/db/schema.ts Outdated
Comment thread packages/cli/src/commands/cli/upgrade.ts Outdated
Comment thread packages/cli/src/commands/cli/upgrade.ts Outdated
@github-actions github-actions Bot added risk: high PR risk score: high and removed risk: medium PR risk score: medium labels Oct 23, 2026
@github-actions github-actions Bot added risk: medium PR risk score: medium and removed risk: high PR risk score: high labels Oct 23, 2026
Comment thread packages/cli/src/lib/db/schema.ts Outdated
@BYK
BYK enabled auto-merge (squash) October 27, 2026 17:17
Switching from nightly back to stable left cli.db at a newer schema. The
older CLI only migrates forward, so its org_regions upsert failed against
the newer composite key and its login lookup found an empty auth table.

- Before `cli upgrade` installs an older version, drop cache tables, copy
  the active account into the legacy auth/user_info tables, and set the
  schema version to 16 so the newer CLI re-migrates when it comes back.
- Make the v18 accounts migration refresh an existing account instead of
  adding a duplicate when it re-runs.
- When a newer CLI wrote the database, rebuild cache tables and record
  this version, or report it in `cli fix` when auto-repair is disabled.
- Compare every table's primary key during repair, not only composite
  keys, and never drop a table whose data cannot be rebuilt.

Fixes #1611
Another invocation of the newer CLI during the download re-migrates the
database and undoes the hand-off, so `cli upgrade` now runs it again after
installing. It skips a database already at schema 16, which keeps any
login the older CLI wrote.

When the older CLI logged out, `auth` comes back empty. The v18 migration
now drops the handed-down active account instead of logging back in.
The post-install pass treated any version above 16 as a re-migration by
the newer CLI, so it also undid forward migrations the older CLI's own
`cli setup` ran. Only this CLI migrates to its own version, so skip
everything below it.
Per review: rather than keep the pre-v18 `auth`/`user_info` schema in
newer CLIs just for downgrades, the hand-off now clears stored accounts
and the user logs in again in the older CLI. Coming back up, the v18
migration folds any login made there into `accounts` as before, so the
token-matching and logout handling it gained in this PR are reverted.

The hand-off only runs when the target predates 0.48.0, the first
release that adapts a newer CLI's database itself.
Running the downgrade hand-off before the download meant a failed
download left the current CLI logged out. It now runs right before the
older binary's `cli setup`, and again in a `finally` after it, so a
setup failure still redoes it if another invocation re-migrated the
database in the meantime.
Tables now count as caches unless they set persistent: true, so only
logins, settings, and other data the CLI cannot fetch again need a flag.
A test lists the cache tables so adding a table is a deliberate choice.
@sentry-junior
sentry-junior Bot force-pushed the fix/cli-db-newer-schema-downgrade branch from 488037c to d814a7d Compare October 27, 2026 17:20
@BYK
BYK merged commit 7b8d032 into main Oct 27, 2026
26 checks passed
@BYK
BYK deleted the fix/cli-db-newer-schema-downgrade branch October 27, 2026 17:37
BYK added a commit that referenced this pull request Oct 27, 2026
…#1631)

Closes #1623. Builds on #1612 (merged), using its hook that runs after
the new binary is downloaded and before it runs `cli setup`.

`sentry cli upgrade` now copies `cli.db` to
`backups/cli-<from>_to_<to>.db` under the config directory before
another version runs setup. The file name is the record: the newest
backup is the last upgrade, so there is no separate manifest. `sentry
cli upgrade undo` reinstalls `<from>` and puts its own database back.
This is the backup-and-restore approach BYK suggested on #1612, so newer
CLIs don't need to rewrite their schema for older ones.

Following [BYK's answers on
#1623](#1623):

- **Logins (merge when possible, warn at least):** the restored database
gets the current `accounts` rows, so logins and refreshed tokens made
since the backup are kept. Accounts removed since then (logged out) stay
removed. When the backup has no `accounts` table (before schema v18), or
the rows can't be copied, the result warns that the user may need to
`sentry auth login`. The result also warns that other changes since the
backup, like defaults, are undone.
- **Retention (one per version):** a version's backup replaces its
earlier one. I also capped them at the five most recent, because nightly
users would otherwise collect one per nightly. The backup an undo would
restore is the newest, so it is never pruned.
- **Other install methods:** undo only covers version changes made by
`sentry cli upgrade` and says so. Downgrades through npm or Homebrew
still rely on #1612's newer-database detection. On a Homebrew install,
which can't install an earlier version, undo points to `sentry cli
upgrade undo --method curl`.
- **Secrets:** backups are `0600` in a `0700` directory, like `cli.db`.

**How it works**
- The backup runs only after the download succeeds and before any #1611
hand-off, so a failed download changes nothing and the backup keeps the
logins. Reinstalling the same version doesn't back up, so it can't
overwrite the record of the upgrade that led there.
- Backups use `VACUUM INTO`, which takes a consistent copy while other
CLI processes use the database. File operations use `node:fs/promises`.
- Undo finds its backup by scanning `backups/`: the newest file by mtime
is the last upgrade, and it only counts if `<to>` is the running
version. `_` isn't valid in semver, so it separates the two versions
unambiguously.
- Undo installs the earlier version on its own channel and doesn't save
a channel change to the current database, because the restored database
already has its own.
- Undo also backs up the current database first, so an undo can be
undone.
- The restore checkpoints the WAL and closes `cli.db`, then swaps in the
copy. For the rest of the run, the upgrading CLI uses a throwaway
in-memory database (`detachDatabase`), so it can't migrate the restored
file forward again. If the swap fails, the current database is left in
place and the #1611 hand-off runs as it would without undo.
- Undo fails with a clear message when there is no backup, or the newest
backup wasn't made upgrading to this version.

**Limitation:** a backup only exists once the version doing the upgrade
includes this change, so undo can't return to 0.47.0 or other older
releases.

**Tests:** `test/lib/db/backup.test.ts` covers backup naming and
pruning, the undo checks, restoring with logins kept, older-format
backups, and that the restored file isn't touched afterwards, with a
full round trip on both the `node:sqlite` and WASM drivers.
`upgrade.test.ts` derives its versions from `CLI_VERSION` with `semver`
and covers the backup existing before setup runs, a full `cli upgrade
undo`, undo refusing when the last upgrade went to another version, the
Homebrew message, and the nothing-to-undo error.

<!-- junior-request-attribution:start -->
via **BYK**.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:slack%3AC010884QHLG%3A1792748852.474329 -->

--

[View Junior
Session](https://junior-prod.sentry.dev/conversations/slack%3AC010884QHLG%3A1792748852.474329)
[[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AC010884QHLG%3A1792748852.474329/?project=4510944073809921)

<!-- junior-session-footer:end -->

---------

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: BYK <burak.kaya@sentry.io>

This branch was previously deployed

1 inactive deployment
Preview — d814a7d4 Deployed Oct 27, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI crashes with "ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint" after switching from nightly back to stable

1 participant