Skip to content

feat(config): follow XDG Base Directory spec for config location - #1503

Open
jared-outpost[bot] wants to merge 13 commits into
mainfrom
issue-1502-xdg-base-dir
Open

feat(config): follow XDG Base Directory spec for config location#1503
jared-outpost[bot] wants to merge 13 commits into
mainfrom
issue-1502-xdg-base-dir

Conversation

@jared-outpost

@jared-outpost jared-outpost Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Closes #1502

What

sentry stored its config/data (the cli.db SQLite database with credentials and caches) and its installed binary under ~/.sentry, cluttering the home directory and breaking in environments that block writes to $HOME (e.g. sandboxed coding agents).

This makes both the config directory and the binary install directory follow the XDG Base Directory specification, matching how the CLI already resolves shell/completion paths, and migrates existing ~/.sentry installs into the new locations on first setup.

Config directory

getConfigDir() resolves via a pure resolveConfigDir(env, home) helper:

  1. SENTRY_CONFIG_DIR — explicit override (highest priority)
  2. Legacy ~/.sentry — used only when it actually holds config (cli.db or config.json), so a bare ~/.sentry/bin from the installer no longer blocks XDG
  3. $XDG_CONFIG_HOME/sentry — defaulting to ~/.config/sentry. A non-absolute XDG_CONFIG_HOME is ignored per the spec.

resolveXdgConfigDir() exposes the XDG target directly (bypassing legacy detection) so migration doesn't no-op while cli.db still sits in ~/.sentry.

Binary install directory

determineInstallDir() now resolves:

  1. SENTRY_INSTALL_DIR — explicit override
  2. $XDG_BIN_HOME — when set to an absolute path, per the XDG spec
  3. ~/.local/bin or ~/bin — when either exists and is already on PATH
  4. ~/.local/bin — default fallback (previously ~/.sentry/bin)

upgrade's known-curl-path detection and its fallback install path track the same resolution, and the install script recognizes XDG_BIN_HOME.

Migration (in sentry cli setup)

On first run, setup migrates the legacy ~/.sentry layout into the XDG locations. Config and binary migration are independent (a failure in one can't skip the other):

  • Config — closes the SQLite DB (it's opened at startup, and an open file can't be renamed on Windows), then moves cli.db + WAL sidecars and config.json into the XDG config dir. Skipped when a target cli.db already exists.
  • Binary — looks for a binary in a genuinely-legacy install dir (getLegacyInstallDirs(), currently just the pre-XDG ~/.sentry/bin) and, when found, copies it into the resolved install target, chmods it executable, records the new path via setInstallInfo, and removes the old copy. ~/.local/bin and ~/bin are valid current XDG targets, so they are never treated as migration sources. setup then adopts the new path so PATH setup and recorded install metadata point at the migrated binary, not the deleted legacy location. Skipped when a binary already exists at the target.

Both migrations use async node:fs/promises and run through the shared bestEffort() helper, so any failure is surfaced as a warning and reported to Sentry (captureException) without ever aborting setup.

sentry upgrade runs setup on the new binary, so it migrates too — but conservatively, because upgrade never edits PATH (it runs --no-modify-path). A legacy ~/.sentry/bin binary is relocated to the XDG install dir only when that dir is already on PATH (resolveUpgradeInstallDir), so the moved binary stays discoverable; setup's legacy-binary migration moves the old binary and removes it before --install writes the new one. If the XDG dir isn't on PATH, upgrade leaves the binary in place — run sentry cli setup explicitly to relocate it and update PATH. Legacy config is migrated on upgrade regardless.

Changes

  • packages/cli/src/lib/db/index.tsresolveConfigDir + new resolveXdgConfigDir helper.
  • packages/cli/src/lib/binary.ts — XDG-aware determineInstallDir (XDG_BIN_HOME, ~/.local/bin fallback).
  • packages/cli/src/lib/upgrade.ts — known-curl-path detection + fallback install path track the XDG resolution.
  • packages/cli/src/lib/binary.ts — shared samePath, LEGACY_INSTALL_SUBDIR, and getLegacyInstallDirs() (used by both setup and upgrade).
  • packages/cli/src/commands/cli/setup.tsmigrateLegacyConfig / migrateLegacyBinary (async, bestEffort-wrapped), adopted into the setup flow.
  • packages/cli/install — install script recognizes XDG_BIN_HOME.
  • Tests: test/lib/config.test.ts, test/lib/binary.test.ts (XDG cases + samePath/getLegacyInstallDirs), test/commands/cli/setup.test.ts (migration + records-new-path + executability), test/commands/cli/upgrade.test.ts (resolveUpgradeInstallDir), and test/e2e/migration.test.ts (spawns the real CLI and verifies config-DB + legacy-binary migration end-to-end).
  • Docs: apps/cli-docs/src/fragments/configuration.md documents config location, binary install location, and migration behavior.

Testing

vitest run for config / binary / setup suites is green (one pre-existing root-only acquireLock permission test fails on main too, since root ignores chmod 0o000). tsc --noEmit clean; Biome clean on changed files.

Review follow-ups addressed

  • BugBot: DB rename on an open handle, stale-path-after-migration, and upgrade-pin/docs over-claim — all fixed.
  • Sentry review bot: copyFileSync permission concern verified (mode is preserved) and hardened with an explicit chmod + test.

Resolve the config/data directory via the XDG Base Directory
specification instead of always using `~/.sentry`. Precedence:

1. `SENTRY_CONFIG_DIR` override (unchanged, highest priority)
2. Legacy `~/.sentry` when it already exists (no breakage for
   existing installs)
3. `/sentry`, defaulting to `~/.config/sentry`

A non-absolute `XDG_CONFIG_HOME` is ignored per the spec. This keeps
credentials and caches out of the home directory root, which also
unblocks environments (e.g. coding agents) that restrict writes to
`/root`.

Fixes #1502
@vercel

vercel Bot commented Aug 28, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
cli Ready Ready Preview Sep 5, 2026 9:59pm UTC

Request Review

@jared-outpost
jared-outpost Bot marked this pull request as ready for review August 28, 2026 17:58
@jared-outpost

jared-outpost Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

CI green on the feature branch (check_suite success). Self-review clean. Marking ready and requesting review from the issue author.

@github-actions github-actions Bot added the risk: high PR risk score: high label Aug 28, 2026
Comment thread packages/cli/src/lib/db/index.ts
…onfig.json

A bare ~/.sentry/bin created by the curl installer should not prevent
new XDG-based installs. The legacy check now requires the presence of
the actual database or the old JSON config file.

Fixes the Cursor Bugbot report on PR #1503.
@BYK

BYK commented Aug 29, 2026

Copy link
Copy Markdown
Member

Jared, we need to do the following:

  1. Move the binary install path to either the new config directory or the appropriate directory based on the XDG Base Directory spec
  2. We should migrate the config directory instead of leaving it at the old place. The best place for this is the setup command I think

@jared-outpost

jared-outpost Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

ack — will update binary install paths to follow XDG (via resolveConfigDir) and add migration logic inside setup so legacy ~/.sentry moves to the new location on first run. expanding scope as requested.

@BYK

BYK commented Aug 29, 2026

Copy link
Copy Markdown
Member

Jared, update the patch already?

@jared-outpost

jared-outpost Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

working on it — expanding to XDG-aware binary paths + migration inside setup. will push the update shortly.

@jared-outpost

jared-outpost Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

yep, expanding the patch now to also move binary install paths to XDG (via XDG_BIN_HOME / ~/.local/bin) and add migration inside setup. will push the update.

@jared-outpost

jared-outpost Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

expanding the patch now to cover XDG binary paths + migration in setup. pushing shortly.

@jared-outpost

jared-outpost Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

not yet — expanding determineInstallDir + setup migration for binary paths now. will push once the XDG-aware logic + migration is in.

@BYK

BYK commented Sep 4, 2026

Copy link
Copy Markdown
Member

Jared, update the patch based on our earlier discussions

…acy layout

Extend the XDG work to the binary install directory and add automatic
migration of the legacy ~/.sentry layout in `sentry cli setup`.

- determineInstallDir now honors an absolute XDG_BIN_HOME (after
  SENTRY_INSTALL_DIR) and falls back to ~/.local/bin instead of
  ~/.sentry/bin.
- setup migrates an existing ~/.sentry/bin binary and legacy config
  (cli.db, config.json) into the XDG locations on first run; skipped
  when the target already exists.
- upgrade's known-curl-path detection and fallback install path track
  the same XDG-aware resolution.
- install script and getCurlInstallPaths recognize XDG_BIN_HOME.
- resolveXdgConfigDir exposes the XDG target (bypassing legacy
  detection) so migration doesn't no-op.
- docs: document binary install location + migration.

Addresses review feedback on #1503.
@jared-outpost

jared-outpost Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

pushed in 90c9cb2 — expanded per your two points:

  1. binary install path now follows XDG. determineInstallDir honors an absolute XDG_BIN_HOME (after SENTRY_INSTALL_DIR) and falls back to ~/.local/bin instead of ~/.sentry/bin. upgrade's known-curl-path detection + fallback and the install script track the same resolution.

  2. migration happens in setup. on first run (including via sentry upgrade), setup moves an existing ~/.sentry/bin binary and legacy config (cli.db, config.json) into the XDG locations, skipping when a target already exists. added resolveXdgConfigDir so the migration target bypasses legacy detection (otherwise it'd no-op while cli.db still sits in ~/.sentry).

tests: new XDG cases in binary.test.ts + a migration suite in setup.test.ts; full run green except one pre-existing root-only failure (acquireLock permission test, fails on main too since root ignores chmod 0o000). docs updated with the install-location + migration behavior.

@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/setup.ts
Comment thread packages/cli/src/commands/cli/setup.ts
Comment thread packages/cli/src/commands/cli/setup.ts Outdated
Reevaluated after scope expanded to include binary install paths.

- Close the SQLite DB before renaming cli.db/WAL sidecars. The DB is
  opened at startup (cleanup-old-binary), and an open file cannot be
  renamed on Windows — previously the whole migration threw and was
  swallowed. Split config and binary migration into separate functions
  with independent try/catch so one failing can't skip the other.
- migrateLegacyBinary now returns the new path; setup adopts it as
  binaryPath/binaryDir so PATH modification and setInstallInfo point at
  the migrated binary instead of the deleted legacy path (a --method
  setInstallInfo no longer overwrites it with the old location).
- docs: clarify that 'sentry upgrade' keeps the binary in place (pins
  SENTRY_INSTALL_DIR) and only 'sentry cli setup' relocates it; config
  data still migrates on upgrade.
- test: record-new-path regression covering the stale-path fix.

Addresses cursor[bot] review on #1503.
# Conflicts:
#	packages/cli/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md
@jared-outpost

jared-outpost Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

merged latest main into the branch in 4148c42 — the conflict (a generated skill reference doc) is resolved and generated files are regenerated against the merged sources. PR is mergeable again; tsc + setup/binary/config tests green locally.

Comment thread packages/cli/src/commands/cli/setup.ts
copyFileSync already preserves the source mode, so a migrated ~/.sentry
binary stays executable in practice. Add an explicit chmodSync(0o755)
after the copy anyway — mirrors installBinary — so the exec bit is never
in doubt even if the legacy copy's permissions were stripped. Covered by
a new accessSync(X_OK) assertion in the migration test.

Also drain the now-flagged silent catch around unlinkSync with a
log.debug.
Previously 'sentry upgrade' hard-pinned SENTRY_INSTALL_DIR to the current
install dir, so a legacy ~/.sentry/bin user could never migrate off it via
upgrade — the docs' claim that upgrade keeps the binary in place was
technically true but meant curl users never reached the XDG location.

Now resolveUpgradeInstallDir relocates a legacy ~/.sentry/bin install to
the XDG install dir when that dir is already on PATH (upgrade runs
--no-modify-path, so it must not move a binary to a dir that isn't on
PATH). setup's migrateLegacyBinary then moves the old binary and removes
it before --install writes the new version; the legacy dir is left empty
so nothing shadows. Non-legacy installs and off-PATH XDG dirs stay put.

- resolveUpgradeInstallDir helper + unit tests (relocate / stay-put /
  undefined PATH / non-legacy).
- docs: describe the conditional relocation on upgrade accurately.
@jared-outpost

jared-outpost Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

good catch — you're right that the old design left curl users stuck on ~/.sentry/bin forever, since upgrade hard-pinned SENTRY_INSTALL_DIR to the current dir and migration then no-op'd.

fixed in 5c07acb: sentry upgrade now relocates a legacy ~/.sentry/bin install to the XDG install dir when that dir is already on PATH (exactly your condition). upgrade runs setup --no-modify-path, so it can't add a new dir to PATH — moving a binary to an off-PATH dir would break the command, so in that case it stays put and an explicit sentry cli setup handles both the move and the PATH edit. setup's legacy-binary migration moves the old binary and deletes it before --install writes the new version, leaving the old dir empty so nothing shadows. non-legacy installs are untouched.

added unit tests for the relocate / stay-put / off-PATH / undefined-PATH cases and updated the docs.

(the windows log you pasted is from an unrelated app — 'Attaquer Taskbar' hitting a WinRT LimitedAccessFeatures COM error — nothing to do with sentry, so no action there.)

Comment thread packages/cli/src/lib/upgrade.ts
resolveUpgradeInstallDir compared a stored install path against a freshly
computed ~/.sentry/bin using strict ===. On case-insensitive filesystems
(Windows, macOS) the two can differ only in casing (e.g. C:\Users\User vs
C:\Users\user) yet point at the same directory, so a legacy install would
fail the equality check and never relocate to the XDG dir.

Add a samePath() helper that lowercases on win32/darwin and use it for both
the legacy-dir and XDG-target comparisons. Test asserts a mixed-case legacy
dir is still treated as legacy on case-insensitive platforms.

Addresses sentry[bot] review on #1503.
Comment thread packages/cli/src/commands/cli/setup.ts Outdated
@jared-outpost

jared-outpost Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

fix-ci: attempt 1 — the only failure is telemetry-exit.test.ts > process exits without waiting for flush timeout (enabledDuration 2729ms vs the ~1950ms budget). This is a timing-sensitive e2e assertion on CLI startup overhead (Sentry init + possible network attempt + CI jitter, per its own comments) and exercises the sentry --version path, which none of this PR's config-dir/setup-migration changes touch. Treating it as flaky and re-ran the failed job once.

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

I think we also need one e2e migration test to make sure the migration is actually working.

Comment thread packages/cli/src/commands/cli/setup.ts Outdated
Comment thread packages/cli/src/commands/cli/setup.ts Outdated
Comment thread packages/cli/src/commands/cli/setup.ts Outdated
Comment thread packages/cli/src/commands/cli/setup.ts Outdated
Comment thread packages/cli/src/commands/cli/upgrade.ts
@BYK BYK added the jared Trigger the Jared agent to work on stuff label Sep 5, 2026
- Binary migration is no longer hardcoded to ~/.sentry/bin: findMigratableBinary
  scans getKnownInstallDirs() (derived from KNOWN_CURL_DIRS) for a binary in any
  known install dir other than the resolved target.
- Deduplicate path logic: samePath and LEGACY_INSTALL_SUBDIR now live in
  binary.ts and are shared by setup and upgrade (removed the duplicate samePath
  in upgrade.ts).
- Use async node:fs/promises (mkdir/copyFile/chmod/rename/unlink) in the
  migration paths.
- Report migration failures to Sentry: both migrations now run through
  bestEffort(), which warns the user and captureException()s while never
  aborting setup.
- Add an e2e migration test (test/e2e/migration.test.ts) that spawns the real
  CLI and verifies both the config DB and a legacy ~/.sentry/bin binary migrate
  to the XDG locations. Plus unit tests for samePath / getKnownInstallDirs.

Addresses BYK's CHANGES_REQUESTED review on #1503.
@jared-outpost

jared-outpost Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — all five review comments are addressed in b03db3b, and threads resolved:

  • e2e migration test: added test/e2e/migration.test.ts, which spawns the real CLI and verifies both the config DB (~/.sentry/cli.db~/.config/sentry) and a legacy ~/.sentry/bin binary migrate to the XDG locations end-to-end.
  • legacy binary not only ~/.sentry: findMigratableBinary now scans getKnownInstallDirs() (from KNOWN_CURL_DIRS).
  • async fs: migration uses node:fs/promises.
  • report to Sentry: both migrations run through bestEffort() (warns + captureException).
  • dedupe: samePath/LEGACY_INSTALL_SUBDIR moved to binary.ts, shared by setup and upgrade.

PR description updated to match.

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

@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 b03db3b. Configure here.

Comment thread packages/cli/src/commands/cli/setup.ts
Address BugBot + Seer findings on the latest revision.

- BugBot (high): findMigratableBinary scanned all known install dirs, so a
  stray binary in ~/.local/bin or ~/bin — both valid *current* XDG targets —
  could be moved out and the original deleted, leaving the real ~/.sentry/bin
  install behind. Replace getKnownInstallDirs with getLegacyInstallDirs, which
  is limited to the genuinely pre-XDG ~/.sentry/bin. Add setup tests asserting
  binaries in ~/.local/bin and ~/bin are never relocated.
- Seer (medium): isInPath used a case-sensitive membership check; on Windows/
  macOS a PATH entry can differ only in casing from a computed dir. Compare
  case-insensitively on those platforms, matching samePath. Add isInPath tests.

Unit + e2e migration tests green; tsc + biome clean.
Comment thread packages/cli/src/lib/binary.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

jared Trigger the Jared agent to work on stuff risk: high PR risk score: high

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support XDG_BASE_DIRECTORY specification.

1 participant