Skip to content

Notify when a newer CLI version is available#5470

Open
simonfaltum wants to merge 23 commits into
mainfrom
simonfaltum/version-update-notice
Open

Notify when a newer CLI version is available#5470
simonfaltum wants to merge 23 commits into
mainfrom
simonfaltum/version-update-notice

Conversation

@simonfaltum

@simonfaltum simonfaltum commented Jun 8, 2026

Copy link
Copy Markdown
Member

Why

PR #5469 added databricks version --check, but most people won't run it. This adds a passive nudge so users on an old CLI find out without having to ask, while making sure a fleet of CLIs can't stampede GitHub and that any failure is silent.

Changes

After a command finishes, the CLI prints a one-line advisory when a newer release is available:

A new release of the Databricks CLI is available: 0.240.0 → 0.245.0
https://github.com/databricks/cli/releases/tag/v0.245.0
To upgrade, run: brew upgrade databricks

Behavior:

  • The latest release is fetched in the background, concurrent with the command so it never blocks it, at most once per day. It is cached via libs/cache (the bundle file cache), namespaced by CLI version so it resets after an upgrade. The notice is shown at most once per day.
  • Process-safe: before fetching, a process must atomically claim a machine-wide O_EXCL sentinel lock (placed in the per-user cache root via the new cache.BaseDir, so another user's lock on a shared machine can't interfere), so when many CLIs start at once only one hits GitHub and the rest skip. A lock older than 30 seconds is treated as abandoned (e.g. the process exited mid-fetch, killing the goroutine before its deferred release ran) and reclaimed. Combined with the 24h cache, a fleet sends at most one request per machine per day. No new dependency: it's stdlib os and works on Linux, macOS, and Windows.
  • Heavily suppressed: development/snapshot builds, non-interactive or CI runs, JSON output, the Databricks Runtime, the version / completion / help commands, when the command errored, when the file cache is disabled (DATABRICKS_CACHE_ENABLED=false), and via DATABRICKS_CLI_DISABLE_UPDATE_CHECK.
  • Any failure (unreachable, timeout, rate-limited) is silent (logged at debug only) and cached like a result, so an offline or airgapped machine doesn't retry GitHub on every command.
  • Robust to running without command setup: help invocations resolve before the root PersistentPreRunE installs cmdio, and tests execute commands without root.Execute's DBR detection. Condition gathering uses the new cmdio.HasIO / dbr.HasDetection probes and treats unknown as "suppress" / "not DBR" instead of panicking.

Structure: the suppression policy is a pure shouldNotify(notifyConditions) function with condition-gathering split out, and rendering (noticeMessage) is split from gating, so both are unit-testable without mocks. (This structure and the Databricks-Runtime suppression were adopted from the alternative exploration in #5482.)

Second PR in the stack, on top of #5469 (merged down into this branch, not rebased).

Test plan

  • Unit tests: shouldNotify policy table (every suppression reason), gatherConditions env/flag wiring (including a bare context without cmdio/dbr detection), command exemption, the single-flight lock (acquire / locked-out / re-acquire / stale-reclaim), and failure caching (no refetch after a failed lookup).
  • Rendering test: a background refresh warms the cache, the notice renders the version delta + release URL once and is throttled the second time the same day; no notice when up to date or when the cache is cold.
  • Manual end-to-end against a mock release server with a release-versioned build: help paths don't panic, version --check (text and JSON) reports the update, the notice shows under a PTY and is throttled on the next run, never appears without a TTY, respects the opt-out, and leaves no lock files behind.
  • Acceptance suite unchanged: dev-build binaries suppress the notice, so existing command output does not drift.
  • ./task fmt, lint-q, and checks clean.

This pull request and its description were written by Isaac.

The version command only printed the current version, with no way to tell whether a newer one was available. This adds an update check that fetches the latest release from GitHub, compares it to the running build, and prints the upgrade command for the detected install method (Homebrew, WinGet, Chocolatey, or the install script).

Exposed as both `databricks version --check` and a `databricks version check` subcommand. Development builds skip the check, and `--output json` is supported for scripting.

Co-authored-by: Isaac
Builds on the `version --check` command (#5469): after a command finishes, the CLI shows a one-line notice when a newer release is available, including the upgrade command for the detected install method.

The latest version is fetched in the background at most once per day (cached via libs/cache, namespaced by CLI version) and the notice itself is shown at most once per day. It is suppressed for development builds, non-interactive/CI runs, JSON output, the version/completion/help commands, on command error, and via DATABRICKS_CLI_DISABLE_UPDATE_CHECK. The background refresh never blocks the command.

Co-authored-by: Isaac
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Approval status: pending

/cmd/root/ - needs approval

Files: cmd/root/root.go
Suggested: @renaudhartert-db
Also eligible: @hectorcast-db, @parthban-db, @tanmay-db, @Divyansh-db, @tejaskochar-db, @mihaimitrea-db, @chrisst, @rauchy

/libs/cmdio/ - needs approval

Files: libs/cmdio/io.go
Suggested: @renaudhartert-db
Also eligible: @hectorcast-db, @parthban-db, @tanmay-db, @Divyansh-db, @tejaskochar-db, @mihaimitrea-db, @chrisst, @rauchy

General files (require maintainer)

8 files changed
Based on git history:

  • @pietern -- recent work in libs/cmdio/, ./, cmd/root/

Any maintainer (@andrewnester, @anton-107, @denik, @pietern, @shreyas-goenka, @renaudhartert-db) can approve all areas.
See OWNERS for ownership rules.

@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Commit: 31f5610

Run: 27292288593

Env 🟨​KNOWN 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
🟨​ aws linux 7 15 261 969 8:24
🟨​ aws windows 7 15 263 967 17:49
💚​ aws-ucws linux 7 15 357 883 7:56
💚​ aws-ucws windows 7 15 359 881 13:02
💚​ azure linux 1 17 264 967 7:16
💚​ azure windows 1 17 266 965 10:53
💚​ azure-ucws linux 1 17 362 879 8:39
💚​ azure-ucws windows 1 17 364 877 12:33
💚​ gcp linux 1 17 260 970 8:03
💚​ gcp windows 1 17 262 968 10:38
22 interesting tests: 15 SKIP, 7 KNOWN
Test Name aws linux aws windows aws-ucws linux aws-ucws windows azure linux azure windows azure-ucws linux azure-ucws windows gcp linux gcp windows
🟨​ TestAccept 🟨​K 🟨​K 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/invariant/no_drift 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/permissions 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions 🟨​K 🟨​K 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=direct 🟨​K 🟨​K 💚​R 💚​R
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 🟨​K 🟨​K 💚​R 💚​R
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions 🟨​K 🟨​K 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=direct 🟨​K 🟨​K 💚​R 💚​R
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 🟨​K 🟨​K 💚​R 💚​R
🙈​ TestAccept/bundle/resources/postgres_branches/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/recreate 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/replace_existing 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/update_protected 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/without_branch_id 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_endpoints/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_endpoints/recreate 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_projects/update_display_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/synced_database_tables/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_indexes/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_indexes/grants/select 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/ssh/connection 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
Top 28 slowest tests (at least 2 minutes):
duration env testname
6:11 azure-ucws windows TestAccept
6:04 azure windows TestAccept
6:01 aws-ucws windows TestAccept
6:00 gcp windows TestAccept
4:16 gcp linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
4:16 gcp linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
4:12 gcp windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
4:04 gcp windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:54 aws-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:51 azure-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:29 aws-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:08 aws-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:05 azure-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:02 azure-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:02 aws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:58 azure linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:54 azure linux TestAccept
2:54 azure linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:52 aws-ucws linux TestAccept
2:52 azure-ucws linux TestAccept
2:52 gcp linux TestAccept
2:48 azure windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:48 aws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:46 aws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:44 azure windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:35 aws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:32 azure-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:27 aws-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct

Cap the release lookup at 2s and, when GitHub is unreachable, times out, or rate-limits, report it gently instead of failing the command: `version --check` now prints that it couldn't reach GitHub and links to the releases page to check manually, and still exits 0.

Co-authored-by: Isaac
If many CLIs start at once on a cold cache, each would otherwise fire its own GitHub request. Add a best-effort, machine-wide single-flight lock (an O_EXCL sentinel file) so only one process fetches at a time; the rest skip silently. Also skip the check entirely when the file cache is disabled. Combined with the 24h cache TTL, a fleet sends at most one request to GitHub per machine per day, and any failure stays silent.

Co-authored-by: Isaac
When a command finishes before the background fetch, the process exits and the goroutine is killed without running its deferred release, leaving the lock file behind. Reclaiming it after 30s (still well above the 3s fetch timeout, so a live fetch is never stolen) shortens the window where the next command skips the refresh. Adds a test for the stale-reclaim path.

Co-authored-by: Isaac
Adopts the cleaner structure from the alternative PR #5482: the suppression policy is now a pure shouldNotify(notifyConditions) function (table-tested with no mocks), condition-gathering is split from the policy, and rendering (noticeMessage) is split from gating so both are unit-testable. Also suppresses on the Databricks Runtime and includes the release-notes URL in the message. Keeps the single-flight lock, opt-out env, libs/cache, and once-per-day display throttle from this PR.

Co-authored-by: Isaac
…pdate-check

# Conflicts:
#	NEXT_CHANGELOG.md
The comment already cited GitHub's recommendation to pin the API version
but no X-GitHub-Api-Version header was actually sent.

Co-authored-by: Isaac
… behavior

gatherConditions called dbr.RunsOnRuntime and cmdio.GetInteractiveMode
unconditionally. Both panic on a context that skipped the usual command
setup, which happens for help invocations (cobra resolves --help and bare
invocations before PersistentPreRunE installs cmdio) and for tests that
execute commands without root.Execute's dbr detection (this broke
cmd/auth tests in CI). Add dbr.HasDetection and cmdio.HasIO probes and
treat unknown as 'suppress' / 'not DBR'.

Also:
- Cache fetch failures (as an empty entry) so offline or airgapped
  machines do not retry GitHub on every command.
- Move the single-flight lock from os.TempDir to the cache root
  (per-user) via the new cache.BaseDir, so another user's stale lock on
  a shared machine can never block this one.

Co-authored-by: Isaac
Review feedback: version check / version --check / --version --check was
too many permutations. Instead, `databricks version` now performs the
check directly. The --version flag (cobra built-in) stays lightweight,
and `version --output json` keeps the build-info shape and skips the
network lookup so scripts are unaffected.

Also drop the always-nil error return from versioncheck.Check; lookup
failures are reported via Result.CheckFailed.

Co-authored-by: Isaac
The base PR folded the check into `version` itself, so the parent-walk
case now uses a real subcommand (completion zsh).

Co-authored-by: Isaac
Settled middle ground: `databricks version` matches `databricks
--version` exactly (no network call, no output change, original help
text restored, which also fixes the help acceptance test). The update
check runs only via `version --check`; the `check` subcommand stays
removed so there is a single way to invoke it.

Co-authored-by: Isaac
simonfaltum added a commit that referenced this pull request Jun 10, 2026
…5469)

## Why

`databricks version` (and `databricks --version`) only prints the
version you're on. There's no way to tell whether you're behind, and
even once you know, the command to upgrade depends on how you installed
the CLI (Homebrew, WinGet, Chocolatey, or the install script). A version
check also shouldn't be fragile: if GitHub is slow or unreachable it
must not hang or fail the command.

## Changes

Before: `databricks version` prints the current version and nothing
else.

Now: `databricks version --check` fetches the latest release from
GitHub, compares it with the running build, and prints the upgrade
command for your install method:

```
Databricks CLI v0.240.0
A new version is available: 0.245.0
To upgrade, run:
  brew upgrade databricks
```

Per review feedback this is the only way to run the check (the `version
check` subcommand from an earlier revision is gone). Bare `databricks
version` and `databricks --version` stay lightweight and identical to
today: no network call, no output change.

Details:

- New `libs/versioncheck` package fetches
`api.github.com/repos/databricks/cli/releases/latest`, semver-compares,
and infers the install method from the executable path (resolving
symlinks so a Homebrew shim classifies as Homebrew, not the install
script).
- The lookup is capped at 2s. If GitHub is unreachable, times out, or
rate-limits, the check fails gently: it prints that it couldn't reach
GitHub and links to the releases page, and still exits 0 instead of
erroring.
- Development and snapshot builds short-circuit without a network call.
- `databricks version --check --output json` returns the structured
result (including `check_failed`) for scripting.

First PR in a two-PR stack; the follow-up (#5470) adds a passive
once-per-day notice.

## Test plan

- [x] Unit tests: install-method detection across OSes, semver
comparison, and the full check against a mock server (update available,
up to date, dev build, server error fails gently, unreachable fails
gently).
- [x] White-box render tests asserting exact output for all states,
including the "couldn't reach GitHub" message.
- [x] Acceptance test (`acceptance/cmd/version`) covering lightweight
`version`, `version --check`, and `version --check --output json`.
- [x] Full local acceptance suite (`go test ./acceptance`), including
the `help` output test.
- [x] `./task fmt`, lint on changed packages, and `./task checks` clean.

This pull request and its description were written by Isaac.
Base automatically changed from simonfaltum/version-update-check to main June 10, 2026 12:31
PR #5469 (version --check) merged to main, so the shared versioncheck
plumbing now comes from main; this branch keeps only the passive-notice
delta (latestRelease struct with the release URL). NEXT_CHANGELOG keeps
only this PR's entry since the v1.3.0 release moved the earlier entries
into CHANGELOG.md.

Co-authored-by: Isaac
The notice is already suppressed in tests by independent gates (dev-build
binaries, pipe-backed stderr, CI env), but runs that download released
binaries (-useversion) should not depend on those staying true. Setting
the opt-out explicitly guarantees no test run can fetch from GitHub or
print the notice into compared output.

Co-authored-by: Isaac
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