// docs · v1.x

Troubleshooting

commitbrief doctor + recipes for common failures — wrong version, missing credentials, malformed JSON, guard aborts, OUTPUT.md template errors.

Updated September 19, 2026

Symptoms and what they usually mean. For anything not on this page, commitbrief doctor is the first stop.

First-stop diagnostic — commitbrief doctor

commitbrief doctor

Runs eight checks against the resolved environment, then per-provider connection pings. Each row is prefixed with a status glyph ( green / yellow / red).

#CheckPass condition
1git binary on PATHexec.LookPath("git") succeeds
2config schema validMerged config non-nil and provider is set
3COMMITBRIEF.md sourceFile present, or falls through to embedded default
4OUTPUT.md template validTemplate parses and executes against empty + sample finding sets
5at least one provider configuredSome provider has an API key (or Ollama is active with base_url)
6active provider has credentialsconfig.provider itself has credentials
7cache directory writableCreate + delete a temp file under <repo>/.commitbrief/cache/
8.commitbrief/ in .gitignoreRepo .gitignore contains the entry

After the synchronous checks, doctor runs a per-provider connection ping for every provider that looks configured. Pings fan out concurrently with a 5-second timeout. A failing ping is a warning, not a fail — one broken provider out of three is recoverable.

That 5 seconds is deliberately impatient, so doctor never leaves you staring at a blank terminal. On a slow link it reports a provider that is merely slow as unreachable — pass --timeout to widen the budget:

commitbrief --timeout 30s doctor

Exit codes: 0 if zero StatusFail rows (warnings allowed), 1 otherwise. Safe to wire into CI as a config-validity gate.

commitbrief doctor --quiet

Suppresses OK rows and the heading — only warnings and failures plus the summary line. Useful in CI logs.

Common issues

git not found on PATH

Doctor reports ✗ git binary on PATH. CommitBrief shells out to git for several diff paths and cannot run without it.

Fix: install git via your platform’s package manager (brew install git / apt install git / winget install Git.Git).

active provider 'X' has no credentials

Doctor flags the active provider check. config.provider points at a provider whose api_key is empty (or base_url for Ollama).

Fix: either run commitbrief setup again to fill in the missing credential, or commitbrief providers use <name> to switch to a provider that already has credentials.

config: <file>: unknown key "…" (allowed: …)

Since v1.17.0, a key in config.yml that the schema doesn’t define is a hard load error instead of the silent no-op every earlier version made it. The likely trigger after an upgrade: a typo like pattern: for regex: under guard.secret_patterns, or a key introduced by a version you’ve since downgraded from.

config: /home/you/.commitbrief/config.yml: unknown key "guard.secret_patterns[0].pattern" (allowed: name, regex)

Fix: rename or remove the key the message names — it also lists the allowed siblings at that level, and, for a top-level typo, a “did you mean” suggestion. If the key exists only to hold a YAML anchor for <<: merging, prefix it x- instead so it’s exempt. To get a run through right now while you fix the file, pass --ignore-unknown-config — it downgrades the failure to a warning for one run and still names every ignored key on stderr, but the key still has no effect, so treat it as a stopgap, not a fix. See Config strictness.

commitbrief --version shows the wrong version

You probably have two binaries on different points of $PATH. Typically: the Homebrew install at /opt/homebrew/bin/commitbrief and a go install build at ~/go/bin/commitbrief. The shell finds whichever is earlier in $PATH.

Diagnose: which -a commitbrief lists every match.

Fix: run the intended binary with an explicit path, reorder $PATH, or uninstall the one you do not want.

commitbrief upgrade warns about exactly this case after a manual replacement: it resolves commitbrief the way your shell would and names both paths — the one it upgraded, and the one your shell will actually run next.

Splash logo shows raw ANSI escapes

Output is being captured / piped through a tool that does not interpret ANSI. The logo prints to stderr and is suppressed when stderr is not a TTY — if you are seeing raw escapes, your stderr is a TTY but the surrounding tooling is rendering it wrong.

Workaround: the logo never goes to stdout, so commitbrief --json | jq and commitbrief > out stay clean. To suppress the logo entirely, set NO_COLOR=1.

Progress spinner repeats a stage line forever (looks like an infinite loop)

The animated progress tree redraws in place by moving the cursor up N lines. Two things break that and make a stage — often Searching for changes… — repeat on a new line every frame:

  1. A line wider than the terminal wraps. It then occupies more physical rows than the renderer counted, so the cursor-up under-shoots and the top line is left behind each frame. The long --with-context security-warning line was the usual trigger. Fixed in v1.4.0 — rendered lines are now clipped to the terminal width so they never wrap.
  2. TERM=dumb (emacs M-x shell, some IDE consoles) reports as a TTY but ignores cursor escapes. v1.4.0 demotes those to plain one-line-per-stage output.

It only looks stuck — the work underneath (often a slow --with-context agent call at the Thinking… stage, capped at five minutes) is running normally. Since v1.4.0 the active stage also shows a live elapsed counter (Thinking… 0:42) so you can see it progressing.

Workaround on older builds: commitbrief --color never … or NO_COLOR=1 commitbrief … switches to plain output that never floods. --quiet silences progress entirely.

LLM produced malformed JSON; falling back to plain-text view

The provider returned a response that did not parse as the v1 findings schema — and the recovery ladder did not rescue it. Since v1.13.0 that ladder is: strip a lone markdown code fence and re-parse (free, no round-trip), then classify the parse error and send one failure-mode-specific repair prompt (a hard “JSON only” reset for prose, or a “complete the JSON” nudge with a raised max_tokens for a truncated attempt). A second failure degrades to markdown rendering and emits this warning.

Run with --verbose, or read meta.retry_count and meta.degrade_reason in --json, to see whether the call retried and why it degraded. Both are live-call-only — a cache replay reports neither.

The plain-text body is still useful as a manual review, but --fail-on skips the threshold check (no structured findings to evaluate) and surfaces:

ℹ --fail-on skipped: LLM produced unparseable output, no findings to evaluate.

Probable causes: model running too cold (rare for current API providers), prompt size near the context limit, Ollama with a small model that does not honor JSON mode well. Switch to a larger model or check whether the diff is near the context window (commitbrief dry-run --verbose reports estimated tokens).

Aborted by pre-send guard

Your diff touches files under .commitbrief/ and either you are in a non-TTY context without --yes, or you answered no to the guard prompt.

Why the guard fires: .commitbrief/ files are usually user-specific (per-repo config, OUTPUT.md template) and committing them may break other developers’ configurations or leak API keys.

Fix: if the change is legitimate, run with --yes (or accept the prompt in TTY). If accidental, unstage the .commitbrief/ files: git restore --staged .commitbrief/.

Possible secrets detected in diff

The pre-send secret scanner found one or more credential-shaped strings on added lines. The scanner names the patterns but never prints the matched substring.

Fix: the most common cause is leaving a real key in source. Rotate the key, remove it from the diff (use environment variables, secret managers, etc.), and re-run.

If the match is a false positive (e.g. a test fixture), pass --allow-secrets to bypass for this invocation, or set guard.secret_scan: false if you have an external secret scanner doing the job. See Safety and cost.

Estimated cost: $X (threshold: $Y)

Cost preflight fired. Default threshold is $0.50.

Fix options:

  • Raise the threshold: commitbrief config set cost.warn_threshold_usd 5.0.
  • Skip the prompt: --no-cost-check.
  • Trim the scope (e.g. --file path/to/specific.go instead of --staged over a huge diff).

See Safety and cost.

argument list too long from a CLI-tool-backed provider

Rare — affects gemini-cli and codex-cli for very large diffs (the combined prompt is passed on the command line and exceeds the platform’s ARG_MAX, typically ~128 KB). claude-cli is not affected — it uses stdin transport.

Fix: for huge diffs, prefer claude-cli or one of the API providers. The diff size limit is a host-CLI constraint, not a CommitBrief limit.

A review dies partway through, on a big diff or a local model

Before v1.16.0 this was the usual cause and there was no knob for it: every provider enforces a hard cap of its own — the CLI-tool providers kill their subprocess after 5 minutes, ollama’s HTTP client after 5, and the Anthropic SDK declines a non-streaming request that could exceed 10 with “streaming is required for operations that may take longer than 10 minutes”.

Fix: ask for more time. --timeout replaces the provider’s cap, so it genuinely lengthens the run rather than just bounding it:

commitbrief --staged --cli claude --timeout 20m
commitbrief config set review.timeout 15m       # every run

If instead the run stops with “timed out after …”, that is your own budget expiring, not the provider — the message says so, and names the duration.

OUTPUT.md template is invalid at …

Your repo-local or user-level OUTPUT.md does not parse or fails to execute against an empty / sample finding set. The pre-send validation catches this before any provider call so a bad template does not burn tokens.

Fix: the error message includes the parse error from text/template. Fix the syntax, or run commitbrief init --force to overwrite with the embedded default.

When in doubt

commitbrief doctor                       # health-check everything
commitbrief dry-run --staged --verbose   # inspect resolution + cost
commitbrief --version                    # confirm what you are running
commitbrief list                         # full reference + current config
commitbrief config show                  # resolved YAML, keys masked

See also