// docs · v1.x

Upgrading — upgrade

commitbrief upgrade checks GitHub Releases and installs a newer CommitBrief using the right mechanism for however the binary got there — delegating to Homebrew, Scoop, or go install, and SHA-256-verifying a manual replacement. No automatic update check, no telemetry.

commitbrief upgrade checks GitHub Releases for a newer CommitBrief and installs it, using the right mechanism for however the running binary got there in the first place. One command works whether you installed via Homebrew, Scoop, go install, or a raw GitHub Releases tarball. New in v1.15.0 (ADR-0034).

commitbrief upgrade          # check, confirm, install
commitbrief upgrade --check  # report only; install nothing

What it does

  1. Resolves the running binary’s real path (symlinks followed — Homebrew’s bin/commitbrief is a symlink into the Cellar) and classifies how it was installed.
  2. Asks the GitHub Releases API for the latest published (non-prerelease) tag.
  3. Compares it against the running version.
  4. If a newer version exists, shows what it’s about to do and asks for confirmation (skipped by --check, --json, or the global --yes).
  5. Acts — either delegating to a package manager, or replacing the binary itself.

The four install methods

Method Detected by What upgrade does
Homebrew resolved path contains a Cellar/commitbrief/ segment runs brew upgrade commitbrief
Scoop resolved path under $SCOOP/apps/commitbrief/ runs scoop update commitbrief
go install resolved path is $GOBIN/commitbrief, $GOPATH/bin/commitbrief, or ~/go/bin/commitbrief runs go install github.com/CommitBrief/commitbrief/cmd/commitbrief@latest
Manual anything else — including a distro package CommitBrief doesn’t publish (nix, AUR, apt) downloads, verifies, and swaps the binary in place

Why delegate instead of always self-replacing? Overwriting a Homebrew- or Scoop-owned binary desynchronizes that manager’s own version bookkeeping — its next upgrade either conflicts with a file it doesn’t recognize as its own, or silently reverts the swap. Delegating keeps every manager’s metadata correct.

Delegated commands inherit stdout/stderr directly: their output is streamed straight through, never parsed, so an upstream format change in brew / scoop / go can’t break CommitBrief. If the owning tool isn’t on PATH, upgrade errors naming which tool it expected.

Misclassification is safe. A package manager CommitBrief doesn’t specifically detect falls into “Manual” by default. Those locations are typically read-only (/nix/store) or root-owned (/usr/bin), so the permission gate below stops the attempt cleanly, before any download, rather than silently corrupting a package-manager-owned file.

Manual replacement

Only a manual install reaches this path.

  1. Permission gate — before any network I/O. CommitBrief probes whether the binary’s directory is writable (replacement is a rename, and rename permission comes from the parent directory, not the file).

  2. Download the release archive for your GOOS/GOARCH plus checksums.txt, over HTTPS.

  3. Verify the archive’s SHA-256 against its line in checksums.txt. A mismatch aborts immediately — the temp file is deleted and the installed binary is left untouched.

  4. Extract only the single binary entry, into a temp file in the same directory as the target, so the final rename stays on one filesystem and is atomic. The existing binary’s file mode is preserved.

  5. Replace. On Unix a single os.Rename swaps the temp file into place — atomic, and legal even while the process is running, since the running binary keeps its own inode. Windows refuses to overwrite a running .exe but allows renaming it, so the live binary is moved aside (targettarget.old) first; a failed swap-in renames the original back. A later upgrade run sweeps away any stale .old.

  6. Verify the result with two independent checks — both warning-only, and both manual-path only:

    • the swapped binary is re-run at its resolved path with --version, bypassing PATH entirely, so a mismatch means the file is wrong;
    • commitbrief is separately resolved the way your shell would resolve it, and compared against the binary just upgraded — this is the check that catches another commitbrief shadowing the new one.

    Neither ever turns a completed upgrade into a failure: the file swap already succeeded.

Permission gate

Before any download, CommitBrief checks whether it can write to the directory holding the binary. If it can’t:

  • Nothing is downloaded. The abort happens before any network request.
  • It prints the exact sudo commitbrief upgrade command, plus the releases page URL, as copyable next steps.
  • CommitBrief never runs sudo itself. Whether to elevate is entirely your call.

--check and --json

--check reports the current version, the latest version, the detected install method, and what action would be taken — then stops. On a manual install it also runs the permission gate, so it reports the install a plain upgrade would actually refuse rather than promising one that would never happen.

--check always exits 0 on any non-error outcome, including “a newer version is available”. The exit-code contract has only two codes, and making “an update exists” a distinct code would be indistinguishable from a genuine failure to any script checking $?. Detect an available update from --json’s update_available field instead.

--json implies --check: it prints exactly one report object and installs nothing, whether or not --check was also passed.

{
  "current": "v1.14.0",
  "latest": "v1.15.0",
  "method": "homebrew",
  "update_available": true,
  "action": "brew upgrade commitbrief"
}

action describes what would run for a package-managed method, or the asset name that would be downloaded for a manual install. When there’s no update, update_available is false and action is empty. On a development build (a locally built binary whose version isn’t parseable semver), --json still emits a report: current carries the raw version string and update_available is false.

Confirmation

Without --check / --json, an available update is confirmed before anything is installed. The global --yes skips the prompt; a non-interactive run (no TTY) without --yes aborts rather than proceeding unattended — the same behavior as commitbrief commit.

Honest limits

  • checksums.txt is unsigned. The trust anchor is TLS to github.com and the CDN it redirects to. That defends against a truncated, corrupted, or inconsistent download — not against a compromised release, since an attacker who could publish a malicious release could publish a matching checksums.txt alongside it. Release-artifact signing is future work, not shipped today.
  • Only the binary is replaced. Bundled man pages are not installed or refreshed by a manual-install upgrade. If you placed man pages yourself, you manage them yourself too.

Privacy

commitbrief upgrade is the only network request CommitBrief makes on its own behalf, and it happens only when you run this command. There is no passive or background version check, no nudge printed after a review, and no config key that would turn one on. The request carries nothing about you: an unauthenticated GET to api.github.com with a commitbrief/<version> User-Agent and nothing else.

See also