# CommitBrief v1.16.0

> One flag. --timeout bounds a whole run and, crucially, raises the hard cap each provider used to enforce invisibly — the CLI tools' 5 minutes, ollama's 5, the Anthropic SDK's 10 — because a deadline alone can only cut a run short, never lengthen it.

Released July 30, 2026

Canonical URL: https://commitbrief.com/changelog/v1.16.0/

---

## Added

- **`--timeout` — bound a run, and raise the ceilings that used to end it early
  (ADR-0038).** Every provider shipped a hard, invisible cap. The CLI-tool
  providers killed their subprocess after **5 minutes**, ollama's HTTP client
  after **5**, and the Anthropic SDK refuses a non-streaming request that could
  exceed **10** — it does not wait, it errors with *"streaming is required for
  operations that may take longer than 10 minutes"*. A large diff or a slow
  local model hit those, and there was nothing to do about it but shrink the
  diff.

  ```sh
  commitbrief --staged --cli claude --timeout 20m   # let the host CLI think
  commitbrief --staged --timeout 600                # bare integer = seconds
  commitbrief config set review.timeout 15m         # make it the default
  commitbrief --staged --timeout 0                  # ignore that default once
  ```

  The interesting part is what `--timeout` is *not*. Wrapping the run in a
  context deadline — the obvious implementation — would have been wrong on its
  own, because a deadline can only ever cut a run **short**. The CLI backend
  derives its own child context from the caller's, so `min(5m, 20m)` still dies
  at five; ollama's `http.Client` timeout fires independently of the context
  entirely; and the Anthropic SDK's ceiling is a pre-flight refusal, not a wait.
  A user who typed `--timeout 20m` and still got killed at 5 would have been
  actively misled. So the value is also handed **down to the provider**, through
  a new optional `TimeoutSetter` interface implemented by exactly the three
  backends that impose a cap of their own. Everyone else — the OpenAI-compatible
  providers, Gemini — purely follows the context, where the deadline already is
  the whole story.

  The value is a Go duration (`90s`, `10m`, `1h30m`) **or** a bare whole number
  of seconds, because `--timeout 600` is what a CI author types and rejecting it
  for a missing unit is a papercut with no upside. Resolution is `--timeout` →
  `review.timeout` → the provider's built-in, so `--timeout 0` restores the
  built-ins for a single run. An invalid or negative value fails **before** the
  diff is read, let alone sent.

  It bounds the **whole run** — diff acquisition, the provider call, render, and
  the time you spend at a confirmation prompt. That last part is deliberate: a
  review parked on a prompt nobody is there to answer is exactly as stuck as one
  parked on a provider. It applies to every command that can spend real time,
  including `doctor` — whose provider probes otherwise fast-fail at 5 seconds,
  reporting a link that is merely *slow* as *unreachable* — and
  `providers test`. On `commitbrief mcp` it becomes a **per-tool-call** budget
  rather than a lifetime for the long-lived server.

  Expiring is a normal failure: exit code 1, with a message that names the
  duration and points back at the flag, so a self-inflicted deadline is never
  mistaken for a provider outage.

- **`review.timeout` config key** — the persistent half of `--timeout`. Stored as
  a string (`timeout: "10m"`) so the YAML stays readable, and **validated on
  write**: `commitbrief config set review.timeout ten-minutes` is rejected there
  rather than breaking every later run.

## Fixed

- The smoke-test script asserted a `dry-run` label the CLI stopped printing
  several releases ago, so `make smoke` had been failing on a clean tree. It sits
  outside `make check` and the release workflow, which is why nothing caught it.
  Developer tooling only — no user-facing behavior changed.