// blog

`commitbrief doctor`: the diagnostic I wish more CLIs shipped.

Eight checks against the resolved environment, plus a per-provider connection ping — what doctor inspects, why warnings are non-blocking by design, and the CI pattern that gates merges on a clean health check.

· CommitBrief

The most common “this isn’t working” message I get about CommitBrief lands in the issue tracker before the user has run commitbrief doctor. I get it — doctor is the kind of command that sounds optional, the kind a careful user would run if they were being thorough but a busy user would skip. The reality is that almost every “this isn’t working” message gets resolved within thirty seconds of doctor’s output, often by the user themselves before I respond.

This post is a small case for that command — what it actually checks, why I made certain failures warnings rather than errors, and how to wire it into CI as a config-validity gate that fails fast and cheap.

What it does in one sentence

commitbrief doctor runs the eight synchronous health checks listed below, then a concurrent per-provider connection ping for every provider that looks configured, and reports a ✓ / ⚠ / ✗ status line for each. Exit code is 0 if zero StatusFail rows, 1 otherwise. Warnings don’t fail.

The whole thing takes one to three seconds depending on how many providers it pings.

The eight checks

The synchronous pass runs in this order, with each check producing exactly one output line:

  1. git binary on PATHexec.LookPath("git") succeeds. CommitBrief shells out to git for several diff paths and the working-tree fallback; without it nothing works. Fail.
  2. config schema valid — the merged config is non-nil and provider is set. This catches the case where you’ve created a config.yml with a syntax error or with provider: unset. Fail.
  3. COMMITBRIEF.md source — the file at <repo>/COMMITBRIEF.md is readable, or the embedded default is used. Fails only on a read error (a corrupted file, a permission problem); a missing file falls through to the embedded default and passes.
  4. OUTPUT.md template valid — the resolved OUTPUT.md template parses as a Go text/template and executes against an empty findings slice plus a sample of one finding per severity. Fails on a malformed template, surfacing the error message from the template parser.
  5. at least one provider configured — some provider has credentials (an API key, or for Ollama, a base_url). Fail if none.
  6. active provider has credentialsconfig.provider itself has credentials, not just some provider. This is the one that fixed the most common confused-user issue from v0.9.0: setting provider: openai while only anthropic.api_key was configured used to pass the looser “some provider” check, then fail every review with API key not set later in the pipeline. Now the active-provider check catches it at doctor-time.
  7. cache directory writable — create a temp file under <repo>/.commitbrief/cache/ and delete it. Fails on permission errors; warns on partial issues (directory exists but the temp-write failed for a different reason).
  8. .commitbrief/ in .gitignore — the repo’s .gitignore contains the entry. Warns (not fails) if missing, because the repo still works without it — you just risk accidentally committing your local config. Fails only if .gitignore exists but is unreadable.

After the eight, the per-provider connection ping fires for every configured provider. Pings run concurrently with a 5-second timeout each. A failing ping is a warning, not a fail, because one broken provider out of three you have configured is recoverable — you can switch with commitbrief providers use <name> and keep going.

Why warnings don’t fail

The warning-vs-fail split is the choice in this design that took the most thought. Three of the eight checks are warnings rather than fails: .commitbrief/ in .gitignore (warn on missing), cache directory writable (warn on partial), and the per-provider ping (warn on individual failure).

The principle: a check warns if the tool can still function without the thing being checked, even if functioning is degraded. It fails if the tool can’t function at all.

The cache directory check is the cleanest example. If the cache directory exists but a temp-write failed for some reason short of a permission denial, the review pipeline still works — it just writes new entries to the failing-temp-write path and the cache misses go through to provider calls. The user notices because reviews are slower than they were yesterday; they don’t notice because reviews stop working entirely. Warning surface is the right shape for that.

The .gitignore check is similar. The repo still works without .commitbrief/ listed; the only consequence is the risk of accidentally committing your local config. That’s a warning-level risk, not a tool-broken risk.

The per-provider ping is the case that drove this design hardest. Pinging every provider concurrently means you regularly catch one of them having a bad day — Anthropic’s API is briefly slow, your local Ollama isn’t running, your Gemini key has been rotated and you forgot to update CommitBrief. None of these should fail doctor as long as the active provider works (which is check #6, a fail). The pings are an extra “here’s what else you have available” surface, and a failed ping for an inactive provider is information, not a problem.

The CI pattern this enables is: commitbrief doctor exits cleanly when the active path through the tool works, regardless of incidental warnings. A build that wants strict gating can pipe through --quiet to suppress OK rows and still get the summary line — but the strict gating is opt-in via grep on the output, not a default.

The output

A passing doctor on a fresh setup looks like:

Doctor — running 9 checks

✓ git binary on PATH                       /usr/bin/git
✓ config schema valid
✓ COMMITBRIEF.md source                    /repo/COMMITBRIEF.md
✓ OUTPUT.md template valid                 built-in default
✓ at least one provider configured         anthropic
✓ active provider has credentials          anthropic
✓ cache directory writable                 /repo/.commitbrief/cache
⚠ .commitbrief/ in .gitignore              .commitbrief/ not listed; run 'commitbrief setup --local' to add it
✓ anthropic connection                     ok (412ms)

9 checks: 8 ok, 1 warning, 0 failed

The detail column on the right does most of the work. built-in default for OUTPUT.md tells you why that check passed without a project-local file. The provider name on the credentials lines confirms which provider was inspected. The ping latency is a quick “is the network OK” data point that’s useful when troubleshooting an actually-slow review later.

--quiet (-q) suppresses the heading and the OK rows, printing only warnings and failures plus the summary. Useful in CI logs where the OK rows are noise.

The CI pattern

Doctor is safe to run as a pre-step in any pipeline that uses CommitBrief, because warnings stay non-blocking. A typical workflow file:

- name: Verify CommitBrief config
  run: commitbrief doctor --quiet
  env:
    ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

- name: Review staged changes
  run: commitbrief --staged --fail-on=critical --no-cost-check
  env:
    ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

This catches “you rotated the API key and forgot to update the CI secret” before any review fires. The doctor step exits 1 if the active provider’s credentials are wrong, which fails the build with a clear error message rather than letting the review step fail in a more confusing way (“API call failed” mid-pipeline is harder to diagnose than “active provider ‘anthropic’ has no credentials” at the start).

A team running CommitBrief across multiple repos might wire doctor into a periodic health-check job rather than every CI run — once a week against all repos, fail loud on any failure, route to the team chat. That’s the pattern for catching config drift before it hits production reviews.

What it doesn’t check

Three things I considered adding and decided against:

  • Disk space remaining. The cache directory check confirms writability but not whether the disk is about to fill. I left this out because it’s the OS’s job to alert on low disk and CommitBrief shouldn’t second-guess that signal.
  • Provider model availability. Doctor confirms credentials but not that the specific model in your config is one the provider currently serves. Adding this would mean shelling out to the provider’s model-list endpoint, which is provider-specific code and not all providers expose it. The first review against an unavailable model fails clearly, which I judged enough.
  • Network reachability beyond the configured provider. The per-provider ping covers the connectivity that matters; a more general “internet works” check would be theatre.

These are the kinds of things a longer-lived doctor command would accumulate. I’d rather have a tight doctor that tells you the truth about what it checks than a sprawling one that gives a false sense of completeness.

Why I think more CLIs should ship one

commitbrief doctor runs in two seconds and answers the question “is my pipeline likely to work?” with high confidence. That’s a small but real piece of UX. The cost was a couple of days of writing the check pipeline and the i18n strings; the benefit is that almost every “X isn’t working” issue I’d otherwise have to debug remotely is self-served by the user before they open the issue.

The pattern is generalisable. If your CLI integrates with an external service, has config that can drift, depends on binaries on PATH, or has any kind of cache or state directory — those are all things a doctor command can check in a way that surfaces the failure mode before the user’s actual workflow hits it. The cost is one command’s worth of code; the benefit compounds over every user who would otherwise have filed an issue.

That’s not a deep insight; it’s just the surface I find most CLIs underinvest in. CommitBrief’s doctor ships because I noticed.

The series so far has worked through the v1.0 surface — the API freeze, the three pre-send guards, the --fail-on CI gate, hooks in GUI clients, the subprocess providers, the diff subcommand collapse, and the JSON schema across providers. Doctor sits at the end of that list because it’s the seam through which all of those features become legibly working or visibly broken on a given developer’s machine. If you’ve read this far and you’ve installed CommitBrief, run it now: commitbrief doctor. The two seconds are worth it.

Related reading


← all posts