# Troubleshooting

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

CommitBrief docs · v1.x · Operations

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

---

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

## First-stop diagnostic — `commitbrief doctor`

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

| # | Check | Pass condition |
|---|-------|----------------|
| 1 | `git binary on PATH` | `exec.LookPath("git")` succeeds |
| 2 | `config schema valid` | Merged config non-nil and `provider` is set |
| 3 | `COMMITBRIEF.md source` | File present, or falls through to embedded default |
| 4 | `OUTPUT.md template valid` | Template parses and executes against empty + sample finding sets |
| 5 | `at least one provider configured` | Some provider has an API key (or Ollama is active with `base_url`) |
| 6 | `active provider has credentials` | `config.provider` itself has credentials |
| 7 | `cache directory writable` | Create + delete a temp file under `<repo>/.commitbrief/cache/` |
| 8 | `.commitbrief/ in .gitignore` | Repo `.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`](/docs/1.x/timeouts#doctor) to widen the budget:

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

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

### `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`](/docs/1.x/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](/docs/1.x/safety-and-cost#secret-scanner).

### `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](/docs/1.x/safety-and-cost#cost-preflight).

### `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`](/docs/1.x/timeouts) replaces the provider's cap, so
it genuinely lengthens the run rather than just bounding it:

```sh
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

```sh
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

- [Configuration](/docs/1.x/configuration) — `commitbrief config`
  and the dotted-path edit surface.
- [Safety and cost](/docs/1.x/safety-and-cost) — guard behaviors
  in detail.
- [Providers](/docs/1.x/providers) — per-provider quirks.
- The
  [GitHub wiki](https://github.com/CommitBrief/commitbrief/wiki)
  for the page-per-topic deep reference.