Skip to content

fix(db): handle readonly database gracefully instead of crashing#235

Merged
BYK merged 11 commits intomainfrom
fix/readonly-db-handling
Feb 17, 2026
Merged

fix(db): handle readonly database gracefully instead of crashing#235
BYK merged 11 commits intomainfrom
fix/readonly-db-handling

Conversation

@betegon
Copy link
Member

@betegon betegon commented Feb 11, 2026

Summary

When the CLI's local database (~/.sentry/cli.db) is on a read-only filesystem or has wrong permissions, DB writes throw SQLiteError: attempt to write a readonly database and crash commands. Now the CLI catches these errors centrally in the traced DB proxy, warns the user once, and continues working — reads still succeed, only caching/persistence is lost.

Closes https://sentry.sentry.io/issues/7258516238/

Changes

Readonly errors are caught in the traced DB proxy (createTracedStatement) alongside the existing schema auto-repair logic. A one-time warning prints to stderr pointing users to sentry cli fix. This single change protects all ~14 DB write paths without touching individual modules.

sentry cli fix now also checks file permissions on the DB file, WAL/SHM journals, and config directory — and repairs them with chmod when possible.

Test Plan

  • bun run typecheck — clean
  • bun run lint — clean
  • 88 tests pass across the 3 changed test files (schema, telemetry, fix command)
  • New tests: isReadonlyError() detection (5 cases), readonly proxy behavior (4 cases: no throw on write, reads work, warns once, message content)

@github-actions
Copy link
Contributor

github-actions bot commented Feb 11, 2026

Semver Impact of This PR

🟢 Patch (bug fixes)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Build

  • Add hole-punch tool to reduce compressed binary size by BYK in #245
  • Add gzip-compressed binary downloads by BYK in #244

Other

  • (args) Parse Sentry web URLs as CLI arguments by BYK in #252

Bug Fixes 🐛

Telemetry

  • Reduce noise from version-check JSON parse errors by BYK in #253
  • Skip Sentry reporting for 4xx API errors by BYK in #251
  • Handle EPIPE errors from piped stdout gracefully by BYK in #250
  • Upgrade Sentry SDK to 10.39.0 and remove custom patches by BYK in #249

Other

  • (db) Handle readonly database gracefully instead of crashing by betegon in #235
  • (polyfill) Add exited promise and stdin to Bun.spawn Node.js polyfill by BYK in #248
  • (upgrade) Remove v prefix from release URLs and work around Bun.write streaming bug by BYK in #243

Internal Changes 🔧

  • (build) Replace local hole-punch script with binpunch package by BYK in #246
  • Use @sentry/api client for requests by MathurAditya724 in #226

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 11, 2026

Codecov Results 📊

✅ Patch coverage is 85.19%. Project has 4126 uncovered lines.
✅ Project coverage is 69.12%. Comparing base (base) to head (head).

Files with missing lines (71)
File Patch % Lines
human.ts 58.29% ⚠️ 395 Missing
resolve-target.ts 20.26% ⚠️ 366 Missing
list.ts 14.39% ⚠️ 345 Missing
api-client.ts 59.38% ⚠️ 262 Missing
list.ts 23.47% ⚠️ 212 Missing
oauth.ts 30.94% ⚠️ 183 Missing
list.ts 21.96% ⚠️ 167 Missing
plan.ts 19.37% ⚠️ 154 Missing
resolver.ts 3.23% ⚠️ 120 Missing
help.ts 19.85% ⚠️ 109 Missing
upgrade.ts 61.37% ⚠️ 107 Missing
view.ts 40.23% ⚠️ 104 Missing
interactive-login.ts 9.17% ⚠️ 99 Missing
errors.ts 5.94% ⚠️ 95 Missing
view.ts 25.81% ⚠️ 92 Missing
view.ts 39.44% ⚠️ 86 Missing
clipboard.ts 4.49% ⚠️ 85 Missing
status.ts 24.07% ⚠️ 82 Missing
migration.ts 47.44% ⚠️ 82 Missing
list.ts 27.18% ⚠️ 75 Missing
browser.ts 4.11% ⚠️ 70 Missing
login.ts 33.33% ⚠️ 64 Missing
span-tree.ts 5.00% ⚠️ 57 Missing
explain.ts 33.33% ⚠️ 56 Missing
api.ts 89.80% ⚠️ 47 Missing
upgrade.ts 66.91% ⚠️ 46 Missing
seer.ts 75.54% ⚠️ 45 Missing
schema.ts 90.00% ⚠️ 39 Missing
refresh.ts 40.63% ⚠️ 38 Missing
seer.ts 79.87% ⚠️ 30 Missing
preload.ts 53.23% ⚠️ 29 Missing
view.ts 87.27% ⚠️ 28 Missing
telemetry.ts 93.01% ⚠️ 27 Missing
utils.ts 88.94% ⚠️ 25 Missing
view.ts 61.54% ⚠️ 25 Missing
fix.ts 89.29% ⚠️ 24 Missing
detector.ts 90.10% ⚠️ 20 Missing
arg-parsing.ts 89.29% ⚠️ 18 Missing
binary.ts 88.67% ⚠️ 17 Missing
list.ts 91.16% ⚠️ 16 Missing
list.ts 90.70% ⚠️ 16 Missing
code-scanner.ts 95.00% ⚠️ 16 Missing
help.ts 57.14% ⚠️ 15 Missing
sentry-client.ts 92.17% ⚠️ 13 Missing
dsn-cache.ts 94.62% ⚠️ 12 Missing
logout.ts 56.00% ⚠️ 11 Missing
token.ts 52.17% ⚠️ 11 Missing
qrcode.ts 33.33% ⚠️ 10 Missing
fs-utils.ts 57.14% ⚠️ 9 Missing
view.ts 94.70% ⚠️ 7 Missing
project-root.ts 97.73% ⚠️ 7 Missing
version-check.ts 92.47% ⚠️ 7 Missing
feedback.ts 84.21% ⚠️ 6 Missing
auth.ts 95.52% ⚠️ 6 Missing
shell.ts 96.23% ⚠️ 6 Missing
app.ts 93.90% ⚠️ 5 Missing
region.ts 86.49% ⚠️ 5 Missing
setup.ts 97.84% ⚠️ 4 Missing
list.ts 97.33% ⚠️ 4 Missing
project-aliases.ts 97.40% ⚠️ 2 Missing
project-root-cache.ts 96.92% ⚠️ 2 Missing
output.ts 89.47% ⚠️ 2 Missing
alias.ts 99.42% ⚠️ 1 Missing
completions.ts 99.37% ⚠️ 1 Missing
index.ts 98.99% ⚠️ 1 Missing
env-file.ts 99.19% ⚠️ 1 Missing
parser.ts 98.63% ⚠️ 1 Missing
colors.ts 98.21% ⚠️ 1 Missing
trace.ts 99.16% ⚠️ 1 Missing
helpers.ts 97.62% ⚠️ 1 Missing
helpers.ts 94.74% ⚠️ 1 Missing
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
+ Coverage    68.74%    69.12%    +0.38%
==========================================
  Files          110       110         —
  Lines        13124     13362      +238
  Branches         0         0         —
==========================================
+ Hits          9021      9236      +215
- Misses        4103      4126       +23
- Partials         0         0         —

Generated by Codecov Action

@betegon betegon marked this pull request as ready for review February 11, 2026 19:47
@betegon betegon requested a review from BYK February 11, 2026 20:50
@betegon
Copy link
Member Author

betegon commented Feb 11, 2026

@BYK lmk your thoughts on this one, i'd like to keep at least the notification to the user telling their sentry config db is readonly.

When the local database file is read-only (wrong permissions, read-only
filesystem, etc.), DB writes now fail silently instead of crashing the
CLI. A one-time warning is printed to stderr pointing users to
`sentry cli fix`.

The fix is centralized in the traced DB proxy (createTracedStatement),
so all ~14 DB write paths are protected without touching individual
modules. Reads continue working normally.

Resolves CLI-4E: SQLiteError: attempt to write a readonly database
Extends `sentry cli fix` to detect and repair file permission issues
on the local database. Checks the config directory (expects 0700), DB
file, and WAL/SHM journals (expect 0600). Attempts chmod repair
automatically; shows manual fix commands if that fails.

This is the recommended fix path shown in the readonly DB warning.
…gDir

- Use getConfigDir() instead of dirname(dbPath) for consistency with
  the canonical config path source
- Remove node:path dirname import (no longer needed)
- Add @param/@returns JSDoc to fix.ts helpers
- Tighten warnReadonlyDatabaseOnce JSDoc (document no-op behavior)
- Use explicit return undefined in proxy readonly handler
- Trim obvious 'what' comments from test setup/teardown
Add 5 tests covering file/directory permission checking and repair:
- Detect readonly database file permissions
- Repair database file permissions (chmod 0444 -> 0600)
- Detect directory permission issues
- Dry-run reports issues without repairing
- Handle combined permission and schema issues

Also extract runFix() helper to reduce test boilerplate.
Coverage for src/commands/cli/fix.ts: 100% functions, 88% lines.
The readonly error handler used a bare `return` for all traced methods,
yielding `undefined` even for all() and values() which callers expect
to return arrays. This would crash on .map()/.length if a write query
ever used these methods (e.g. DELETE ... RETURNING *).

Extract handleReadonlyError() helper that returns [] for all/values
and undefined for run/get. Also reduces complexity in the proxy handler.
@BYK BYK force-pushed the fix/readonly-db-handling branch from 64f244b to 300ceb8 Compare February 16, 2026 23:17
- fix.ts: convert to async (stat/chmod from node:fs/promises), parallel
  checks with Promise.all/Promise.allSettled, inline fixFunc into
  buildCommand, explicit ENOENT handling
- telemetry.ts: use let+noop self-replacing pattern for one-shot
  warning, add tryRepairReadonly() that chmods files for future
  commands before falling back to user warning
- fix.test.ts: await async func.call() invocations
- telemetry.test.ts: update readonly tests for auto-repair behavior
- Use exact mode match instead of bitmask in checkMode (prevents extra
  permission bits from passing validation)
- Wrap handleSchemaIssues in try/catch so --dry-run doesn't crash on
  readonly databases
- Repair config directory permissions in tryRepairReadonly alongside
  database files
- Extract chmodIfExists helper that only catches ENOENT, re-throws
  other errors instead of swallowing them
- Update tests: chmod db files to 0o600 after creation to match
  production setDbPermissions behavior
…ssues

Previously, when handleSchemaIssues threw and no permission issues were
detected, totalFound was 0 and the command reported 'No issues found'
despite the schema check failure. Now anyFailed is checked alongside
totalFound to ensure failures are always surfaced with a non-zero exit.
When the config directory lacks execute permission, stat on child files
(db, wal, shm) fails with EACCES instead of returning mode bits. Treat
EACCES like ENOENT (skip the file) since the directory permission check
will catch the root cause.
Copy link

@cursor cursor bot left a comment

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.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

BYK added 2 commits February 17, 2026 00:07
Directory chmod must complete before child file chmod calls, otherwise
file repairs can fail with EACCES when the parent directory lacks execute
permission. Split repairPermissions into directory-first (sequential)
then files (parallel) to prevent the race.
Add fix command tests:
- Schema check failure with no permission issues sets exitCode=1
- Dry-run with schema failure sets exitCode=1
- Schema check suppresses error when permission issues explain failure
- Schema repair success path (missing columns → repaired)

Add telemetry test:
- Readonly warning fallback when auto-repair fails (mock chmodSync
  to simulate unowned files)
@BYK BYK merged commit 395aede into main Feb 17, 2026
23 checks passed
@BYK BYK deleted the fix/readonly-db-handling branch February 17, 2026 00:56
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