Providers
The review pass runs against one LLM provider. Providers are config-driven: there are two first-class kinds (anthropic and openai) and any specific vendor (GLM via z.ai, DeepSeek, Moonshot, a self-hosted gateway, any Anthropic- or OpenAI-compatible endpoint) is just a named profile of one of those kinds. New vendors are added by config alone, no rebuild.
Nothing here is persisted; see Credentials.
Layering
Section titled “Layering”Settings resolve in this order, highest wins:
CLI flags > environment > config file > built-in defaultsThe optional config file lives at ~/.config/miu/cr/config.toml (same on macOS and Linux), alongside the SQLite state DB at ~/.config/miu/cr/state.db, matching the miu family convention.
It is entirely optional; miu-cr works with zero config. A fully commented starter is in config.example.toml.
Choosing a provider
Section titled “Choosing a provider”--provider takes a profile name: a built-in (anthropic, openai), any name you defined in the config file, or auto (the default).
With auto, miu-cr picks OpenAI when OPENAI_API_KEY is set and no Anthropic credential is present (--api-key, --auth-token, ANTHROPIC_API_KEY, or ANTHROPIC_AUTH_TOKEN); otherwise it uses default_provider from the config file (Anthropic by default). Anthropic is the default because it backs both the native API and Anthropic-compatible gateways.
Config schema
Section titled “Config schema”default_provider = "anthropic" # profile to use when --provider is omitted
[providers.<name>]kind = "anthropic" # or "openai"; the first-class familybase_url = "https://…" # optional; gateway/endpoint overridemodel = "…" # optional; default model for this profileauth_env = "MY_TOKEN" # RECOMMENDED; NAME of an env var holding the credentialauth_command = ["gopass", "show", "-o", "ai/provider"] # argv only; stdout is the tokenauth_token = "…" # discouraged literal credential; plaintext on diskauth = "bearer" # "bearer" | "api_key" | "oauth" | omit for legacy autoThe two built-in profiles anthropic and openai always exist; you only declare a [providers.<name>] block to add a vendor or override a built-in’s model/base_url. Standard env vars and CLI flags still override profile credentials.
Profile credential precedence is auth_token > non-empty auth_env > auth_command.
auth_command executes the argv directly, not through a shell; stdout is trimmed
as the token and stderr is omitted from errors because it may contain secrets.
kind selects the protocol family. auth selects the credential mechanism:
auth | Valid kind | Meaning |
|---|---|---|
bearer | anthropic | Profile credential is sent as Authorization: Bearer ...; use for Anthropic-compatible gateways. |
api_key | anthropic, openai | Profile credential is sent as the provider API key (x-api-key for Anthropic, OpenAI API-key slot for OpenAI-compatible). |
oauth | openai | Use miucr login / ChatGPT-plan OAuth; profile static credentials are rejected. |
| omitted | both | Legacy auto: Anthropic profile credentials are Bearer; OpenAI uses profile key > OAuth > ambient OPENAI_API_KEY. |
Credential source precedence is auth_token > non-empty auth_env > auth_command. auth_command is an argv array executed directly, never through a shell; it must print exactly one credential line to stdout. If a selected auth_command fails, resolution fails instead of silently falling through to OAuth or ambient env keys; stderr is omitted from the error because secret helpers may print credentials there.
Per-provider usage quota: [providers.<name>.quota]
Section titled “Per-provider usage quota: [providers.<name>.quota]”Optionally cap how much a provider instance may be used over a recurring window. There is no quota by default (a provider with no quota block is uncapped). The cap is per instance and aggregates across every review/repo that uses it, so one block limits, say, the pricier OAuth provider everywhere at once.
[providers.openai.quota]dimension = "tokens" # tokens (default; input incl. cache + output) | requests (one per review)limit = 2_000_000 # cap in the chosen dimension; must be > 0window = "5h" # a Go duration (1h, 5h, 24h, 168h) OR "monthly" (calendar month, UTC)- Dimension —
tokensmeters all tokens processed — uncached input + cache-read + cache-creation + output (so cached input is not undercounted);requestscounts reviews. (cost/$ is not yet supported.) - Window — a fixed window: a Go duration bucketed off the epoch (so
5hresets every 5 hours on fixed boundaries,24hdaily), ormonthlyfor a calendar month. Hourly and 5-hourly windows are first-class. Changing the window starts a fresh bucket. - Enforcement — fail-closed and hard: before each review the accumulated usage for the current window is checked; at/over the limit the review is blocked with a typed
quota.exceedederror (and a one-shot warning at ≥80%). A counter that can’t be read/opened also blocks, but as a retryablestore.unavailable(notquota.exceeded) so a transient DB outage is retried, not mistaken for a hit. On the serve host, a genuine quota-blocked PR is skipped and logged (the poller keeps running); a later push or comment re-triggers a fresh job that re-checks the window. - State — usage counters persist in the same store as history (
state.dbfor the CLI, Postgres for the host), surviving one-shot CLI invocations. A baddimension/window/limitis a typedconfig.invalid(exit2). - Metering scope — the counter records every LLM call in a review — the main pass, parallel subagents, and the optional
--patch-repairsecond pass — summed. Usage is captured with its cache breakdown: uncached input, cache-read, and cache-creation are all metered (Anthropic/z.ai report cache as separate buckets; OpenAI reports cached tokens as a sub-count of prompt tokens, normalized so cached input is counted exactly once). Review stats exposeinput_tokens,output_tokens,cache_read_tokens,cache_creation_tokens,total_input_tokens, andcache_hit_ratiofor usage optimization. (Codex/ChatGPT-plan reviews report no usage and meter as zero.)
The host config (host.yaml) takes the same block under each providers.<name>.
Review defaults: [review]
Section titled “Review defaults: [review]”The optional [review] table sets defaults for miucr review flags. An explicit flag always wins; a [review] value only fills a flag you did not pass. A bad enum or timeout is a typed config.invalid error (exit 2).
[review]gate = "high" # CLI default --gate: none|info|low|medium|high|criticalfilter_mode = "diff_context" # CLI default --filter-mode (--pr): added|diff_context|file|nofiltermin_severity = "low" # optional --min-severity inline floor; unset means no floorformat = "full" # CLI default --format: full|minimaltimeout = "900s" # review timeout (a Go duration; CLI review default is 900s)stalled_timeout = "5m" # no-progress watchdog; "0s" disables itexpand = 5 # CLI default --expand; --deep-context raises it to 20token_budget = 0 # CLI default --token-budget; 0 = no capdeep_context = false # CLI default --deep-context# context_hops = 3 # optional override; omit to let deep_context choose automaticallyconversation = false # CLI default --conversation on --prthinking = "auto" # auto|off|low|medium|high. auto = extended thinking/reasoning # ON when the model supports it (Claude, gpt-5/o-series, codex) — # deeper analysis. Off (or unsupported model) falls back to # temperature. Thinking omits temperature (it forces temp 1).temperature = 0 # LLM sampling temperature (0–2), used when thinking is OFF. # Default 0 = deterministic: re-reviews of the same diff stay # stable instead of churning findings. Applies to anthropic + # openai chat models; reasoning models (which need temp 1) ignore it.suggest = false # CLI default --suggest (Action input defaults true)patch_repair = false # CLI default --patch-repair; requires suggest=true
[review.provider_retry] # provider API retry behaviormax_retries = 10 # retries after the first failed provider request; 0 disablesinitial_backoff = "5s" # jittered exponential backoff starts heremax_backoff = "2m" # cap each provider retry sleepmax_elapsed = "10m" # cap total retry sleep budget inside the review timeout
[review.tools] # built-in reviewer tool behaviormax_retries = 2 # transient tool execution retries; 0 disablesmax_turns = 24 # max model tool turns before forced finalization; 0 uses default, capped at 64retry_backoff = "250ms" # initial retry delay, doubled per retry and capped
[review.tools.symbol_context] # internal symbol_context tool limitsmax_bytes = 16000 # cap returned text before it reaches the modelmax_files = 2000 # cap repo-wide files scanned per tool callmax_parallel = 8 # bounded workers for large repo-wide scans
[review.subagents] # optional scoped fanout inside one reviewmode = "auto" # off|auto|alwaysmax_parallel = 2 # default 2, capped at 8min_files = 8 # auto threshold; 0 uses defaultmin_context_bytes = 60000 # auto threshold; 0 uses defaultrequire_all = true # failed subagent prevents approval/check success
[review.approval] # optional default for --approval on PR --postmode = "off" # off|clean|thresholdmax_priority = "P4" # threshold mode only; P0|P1|P2|P3|P4note = "on_findings" # none|on_findings|always
[[review.subagents.agents]]name = "go"include = ["**/*.go"]exclude = ["**/*_test.go"]system_prompt = "Focus on correctness, concurrency, error handling, and API compatibility."
[review.category_urls] # map a finding Category → a docs URL (clickable link + SARIF helpUri)"security" = "https://example.com/docs/security"timeout caps total review wall clock. stalled_timeout cancels a run that
stops emitting progress, which catches stuck provider/tool turns sooner in host
mode; set it to "0s" only when debugging. [review.provider_retry] retries
transient provider failures (429, 5xx, 529, and temporary transport
errors) with bounded jittered exponential backoff. It does not retry auth,
invalid request, content-policy, or context cancellation errors. [review.tools]
sets the agent tool-loop budget and retries transient tool execution failures
such as temporary filesystem/process errors, but does not retry malformed tool
calls, unknown tools, missing files, or context cancellation.
symbol_context is part of the core read-only reviewer toolset, alongside
file_read and grep. [review.tools.symbol_context] tunes its bounds. The
internal revision-pinned scanner can fetch bounded document_symbols,
definition, references, incoming_calls, outgoing_calls,
implementations, dependencies, and lightweight Astro/Vue/Svelte component
symbols. It also supplies a compact changed-symbol prelude before the model
turn, capped separately to a small first-pass slice of changed files. Large
repo-wide scans use up to max_parallel bounded readers while preserving
deterministic output order. It reads the same reviewed git revision as the other
tools, so PR and staged reviews do not inspect the live worktree. The tool is
intended for code-intelligence lookups first; use grep for raw text and
file_read for exact lines after a target is located.
Only these review attributes can be defaulted from config; there is intentionally no post or force config because write-action and repeat-spend defaults are footguns. Approval remains explicit as a policy object so host configs can choose strict clean approval or a priority threshold per repo.
Viewing and editing config (config show / set / edit)
Section titled “Viewing and editing config (config show / set / edit)”miucr config show prints the effective configuration with every credential masked (auth_token, the store dsn) by structural redaction, so a token can never reach stdout. By default it shows only your user-set values; --all includes the built-in defaults.
miucr config show # user-set values only (secrets redacted)miucr config show --all # full effective config incl. built-in defaultsmiucr config show -o pretty # TOML view for humansconfig set <key> <value> merges one dotted, non-secret scalar key (e.g. default_provider, review.gate, providers.zai.model, providers.zai.auth) into the existing config without re-running init; secret keys (auth_token, store.dsn) are rejected to avoid a plaintext-secret footgun. Use config edit or edit the file directly for array fields like auth_command. config edit opens ~/.config/miu/cr/config.toml in $VISUAL/$EDITOR (it needs an interactive terminal; in CI use config set).
Anthropic
Section titled “Anthropic”export ANTHROPIC_API_KEY=...miucr review --stagedResolution order (first non-empty wins):
| Setting | Flag | Env | Profile | Default |
|---|---|---|---|---|
| API key (x-api-key) | --api-key | ANTHROPIC_API_KEY | - | required unless an auth token is set |
| Auth token (Bearer) | --auth-token | ANTHROPIC_AUTH_TOKEN | auth_token / auth_env / auth_command with auth = "bearer" | - |
| Base URL | --base-url | ANTHROPIC_BASE_URL | base_url | SDK default |
| Model | --model | ANTHROPIC_MODEL | model | claude-sonnet-4-5-20250929 |
The API key is sent as x-api-key. An auth token is sent as a Bearer Authorization header instead, what Anthropic-compatible gateways expect.
GLM via z.ai (Anthropic-compatible): example profile
Section titled “GLM via z.ai (Anthropic-compatible): example profile”z.ai exposes an Anthropic-compatible gateway, so it’s an anthropic-kind profile with a base URL + bearer token. There is no z.ai-specific code; it is purely configuration.
[providers.zai]kind = "anthropic"base_url = "https://api.z.ai/api/anthropic"model = "glm-5.2"auth = "bearer"auth_env = "ZAI_API_KEY"# auth_command = ["gopass", "show", "-o", "ai/zai"]export ZAI_API_KEY=...miucr review --staged --provider zaiEquivalent without a config file, using the generic Anthropic env vars:
export ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropicexport ANTHROPIC_AUTH_TOKEN=$ZAI_API_KEYmiucr review --staged --model glm-5.2…or entirely via flags:
miucr review --staged \ --base-url https://api.z.ai/api/anthropic \ --auth-token "$ZAI_API_KEY" \ --model glm-5.2OpenAI (and OpenAI-compatible)
Section titled “OpenAI (and OpenAI-compatible)”export OPENAI_API_KEY=...miucr review --staged --provider openaiResolution order:
| Setting | Flag | Env | Profile | Default |
|---|---|---|---|---|
| API key | --api-key | OPENAI_API_KEY | auth_token / auth_env / auth_command with auth = "api_key" | required |
| Base URL | --base-url | OPENAI_BASE_URL | base_url | https://api.openai.com/v1 |
| Model | --model | OPENAI_MODEL | model | gpt-4o |
Requests send max_tokens (not max_completion_tokens) for the broadest compatibility with OpenAI-compatible gateways. --auth-token is Anthropic-only; passing it with an OpenAI provider is a typed error.
OAuth / your ChatGPT plan
Section titled “OAuth / your ChatGPT plan”The OpenAI provider can also authenticate without an API key by reviewing on your ChatGPT plan. miucr login caches an OAuth token and subsequent OpenAI reviews talk to the codex backend (the ChatGPT-plan Responses protocol). On that path the model defaults to gpt-5.5; precedence is --model > MIUCR_CODEX_MODEL > an explicit model in your [providers.openai] profile > gpt-5.5. miucr init writes model = "gpt-5.5" for you on the OAuth path so the codex model is visible and editable. The pinned gpt-4o/OPENAI_MODEL default never applies here: the codex backend rejects api.openai.com models, so a config model = "gpt-4o" is ignored and falls through to gpt-5.5.
When auth is unset, the OpenAI credential resolves intent-ordered so an ambient OPENAI_API_KEY (often set for other tools) never silently overrides a deliberate choice:
- a profile-configured key (
auth_env/auth_command/auth_token) or--api-key; - a cached
miucr login(OAuth → codex / ChatGPT-plan backend); - an ambient
OPENAI_API_KEYenv var.
Pin the method with auth = "oauth" or auth = "api_key" to skip the auto order. With auth = "oauth", remove auth_env, auth_command, and auth_token from the profile. See Credentials → Using your ChatGPT plan.
Generic OpenAI-compatible gateway: example profile
Section titled “Generic OpenAI-compatible gateway: example profile”[providers.my-gateway]kind = "openai"base_url = "https://gateway.example.com/v1"model = "your-model-id"auth = "api_key"auth_env = "MY_GATEWAY_TOKEN"# auth_command = ["op", "read", "op://vault/item/token"]export MY_GATEWAY_TOKEN=...miucr review --staged --provider my-gatewayMissing credentials
Section titled “Missing credentials”If no credential is found for the resolved provider, the review fails with a typed error (exit 1) and a hint, e.g.:
no Anthropic credentials: set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN, configure a provider in <config path>, or pass --api-key / --auth-token