# 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 docs · v1.x · Getting Started

Canonical URL: https://commitbrief.com/docs/1.x/upgrade/

---

`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).

```sh
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 (`target` → `target.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](/docs/1.x/severity#exit-codes) 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.

```json
{
  "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`](/docs/1.x/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

- [Installation](/docs/1.x/installation) — the four install methods this
  command detects and acts on.
- [Severity and CI gating](/docs/1.x/severity#exit-codes) — the two-code
  contract `--check` respects.
- [Troubleshooting](/docs/1.x/troubleshooting) — when `--version` reports
  something you didn't expect.