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
- Resolves the running binary’s real path (symlinks followed — Homebrew’s
bin/commitbriefis a symlink into the Cellar) and classifies how it was installed. - Asks the GitHub Releases API for the latest published (non-prerelease) tag.
- Compares it against the running version.
- If a newer version exists, shows what it’s about to do and asks for
confirmation (skipped by
--check,--json, or the global--yes). - 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.
-
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).
-
Download the release archive for your
GOOS/GOARCHpluschecksums.txt, over HTTPS. -
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. -
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.
-
Replace. On Unix a single
os.Renameswaps 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.exebut allows renaming it, so the live binary is moved aside (target→target.old) first; a failed swap-in renames the original back. A laterupgraderun sweeps away any stale.old. -
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, bypassingPATHentirely, so a mismatch means the file is wrong; commitbriefis separately resolved the way your shell would resolve it, and compared against the binary just upgraded — this is the check that catches anothercommitbriefshadowing the new one.
Neither ever turns a completed upgrade into a failure: the file swap already succeeded.
- the swapped binary is re-run at its resolved path with
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 upgradecommand, plus the releases page URL, as copyable next steps. - CommitBrief never runs
sudoitself. 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.txtis unsigned. The trust anchor is TLS togithub.comand 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 matchingchecksums.txtalongside 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 — the four install methods this command detects and acts on.
- Severity and CI gating — the two-code
contract
--checkrespects. - Troubleshooting — when
--versionreports something you didn’t expect.