FAQ
Common errors, surprises, and "is this supposed to do that?" questions. If your problem isn't here, open a GitHub issue with the steps you ran and the full output.
Search before you scroll
Use the search box in the header to jump straight to an error string or flag name. The page is grouped by symptom, so the fastest path is usually to search for the exact message you see in your terminal.
Configuration & startup¶
no valid LLM endpoint configured¶
no valid LLM endpoint configured; one of KAID_LLM_URL/KAID_LLM_TOKEN/KAID_LLM_MODEL,
~/.kaid/config.json, or ANTHROPIC_BASE_URL/ANTHROPIC_AUTH_TOKEN/
ANTHROPIC_MODEL must be set
kaid ran the full endpoint-resolution chain (Configuration)
and didn't find a complete (URL, token, model) triple. Either:
- Run
kaid config set llm.url …/llm.auth_token …/llm.model …to populate~/.kaid/config.json, or - Export
KAID_LLM_URL/KAID_LLM_TOKEN/KAID_LLM_MODEL, or - Export
ANTHROPIC_BASE_URL/ANTHROPIC_AUTH_TOKEN/ANTHROPIC_MODELif you already use an Anthropic-compatible client.
Then kaid llm test to verify connectivity before retrying the review.
kaid llm test shows the wrong source¶
kaid uses the first complete triple, not the last. So if your
config file has all three llm.* keys, env vars are ignored. To make
env wins, either delete the config keys (rm the file or unset by
hand) or use kaid config set to switch to the new values.
401 / 403 from kaid llm test¶
The token is missing scope, expired, or wrong vendor. Anthropic and
OpenAI use different auth headers and different URL shapes — make sure
llm.use_anthropic matches the URL you're pointing at:
- Anthropic: URL ends
/v1/messages,use_anthropic=true. - OpenAI / OpenAI-compatible: URL ends
/v1/chat/completions,use_anthropic=false.
not a git repository¶
kaid review runs git diff (and git ls-files for untracked files)
against the current directory. If you're not inside a Git working tree,
it exits early. Either cd into a repo, or pass --repo /path/to/repo.
"No tool calls parsed" (local models / Ollama)¶
[kaid] No tool calls parsed for src/foo.go, retrying...
[kaid] Max tool requests reached for src/foo.go.
If every review loops through No tool calls parsed retries and ends
with "Max tool requests reached" and zero comments, the model — not the
config — is the problem. kaid drives the review entirely through tool
calls, so the model must support native tool calling (function
calling). A model that merely narrates tool calls in its text
output (or inside <think> blocks) can never work with kaid, no matter
how the prompt is tuned — deepseek-r1 is a common example. Models
with native tool support, such as qwen3, work fine. For Ollama, pick
from the models tagged with tools support:
https://ollama.com/search?c=tools.
Verify a local model directly, without kaid in the loop:
curl http://127.0.0.1:11434/v1/chat/completions -H "Content-Type: application/json" -d '{
"model": "qwen3:32b",
"messages": [{"role": "user", "content": "The code below has a bug, use the report_bug tool to report it.\n\nfunc add(a, b int) int {\n return a - b\n}"}],
"tools": [{"type": "function", "function": {"name": "report_bug", "description": "Report a bug in the code",
"parameters": {"type": "object", "properties": {"line": {"type": "integer"}, "description": {"type": "string"}}, "required": ["description"]}}}]
}'
Pass: the response contains a structured tool_calls array naming
report_bug. Fail: the "call" appears as text inside content.
If the model does support tools but responses are slow on local hardware, raise the LLM timeout instead — see Timeouts.
Filtering & rules¶
My file isn't being reviewed¶
Run kaid review --preview (no LLM cost). The output shows the reason
each candidate file was kept or dropped. Files under provider directories
such as vendor/ and node_modules/ collapse into one summary line in the
terminal; kaid review --preview --format json still lists every entry:
src/foo.go modified
src/foo_test.go modified (excluded: user_exclude)
assets/logo.png binary (excluded: unsupported_ext)
3 file(s) in provider directories (node_modules/) — not reviewable
The exclusion reasons map to gates in the file filter:
| Reason | Fix |
|---|---|
binary |
Nothing to do — binary files have no reviewable text. |
user_exclude |
Remove the pattern from your exclude list. |
unsupported_ext |
Add the extension to your include list to bypass the allowlist gate. |
default_path |
Add the file to include — that overrides built-in test-file exclude patterns. |
provider_directory |
Nothing to do — provider directories such as vendor/ and node_modules/ are never reviewable, even when included. |
deleted |
Nothing to do — there's no new content to review. |
too_large |
The diff alone exceeds 80% of max_tokens. Raise --max-tokens (or the saved max_tokens), or split the change into smaller commits. |
My custom rule isn't firing¶
Run kaid rules check <file-path>. It prints the layer and
glob pattern that matched, end-to-end:
If the layer is wrong (e.g., showing "System built-in" when you expected
your project rule), most likely the declaration order matters — the
first matching pattern wins. Move your more-specific rule earlier in the
rules array, or fix the glob.
Brace expansion isn't working¶
bmatcuk/doublestar/v4 supports {ts,tsx} braces. If they're not
matching, check for stray spaces — {ts, tsx} with a space silently
fails to match tsx.
Reviews¶
A file shows zero comments — was it actually reviewed?¶
Open the Session Viewer (kaid viewer), find the session,
and look at the main_task lane of the group that contains the file
(groups are keyed by their file paths, so a file reviewed on its own
appears under its own path):
- Tool calls present + ends in
task_done→ reviewed cleanly. - Tool calls present + ends mid-loop → look for an error card.
- No
main_taskcards at all → the file was filtered out before review; see Filtering & rules above.
Comments have start_line: 0 and end_line: 0¶
kaid couldn't anchor the comment to a precise line in the diff. Two common causes:
- The model paraphrased
existing_codeinstead of copying it verbatim from the diff. The model is told not to, but it sometimes does. - The diff had unusual formatting (CRLF, mixed tabs/spaces) that broke the sliding-window match.
The comment is still real — it just wasn't placed automatically. Most
agent integrations read the
existing_code field and locate the spot in the file themselves.
Token threshold exceeded¶
[kaid] WARNING: prompt tokens (240000) exceed 80% of max_tokens(200000) [round 1] for group "src/big.sql"
The initial prompt for that group (rule + diffs + change-files list) was
already past 80 % of MAX_TOKENS = 200000 before the model could even
respond. kaid skips the group and continues — you'll see this in
warnings in JSON mode too.
MAX_TOKENS is the prompt ceiling only. The model's output is capped
independently by MAX_COMPLETION_TOKENS (16384), so this warning is
always about input size.
Mitigations:
- Add the file to your
excludelist if it's autogenerated. - Split a large refactor into smaller commits.
- Use
--commitmode for a series of small commits rather than reviewing them all at once via workspace mode.
Plan phase took forever and the file is small¶
Run kaid review --preview first. The plan phase runs when either
threshold is crossed:
- The largest file in the group changed at least
PLAN_MODE_LINE_THRESHOLDlines (default 50), or - The group holds 2+ files and their combined
lines.changedreachesPLAN_MODE_GROUP_LINE_THRESHOLD(default 100).
So a small file can still get a plan pass if it was grouped with other
changes that add up. That's by design — large or wide diffs benefit
from a planning pass. To skip it for a single review, run with a
smaller diff, or temporarily edit the embedded template (advanced;
you'll need to override --tools).
"Max tool requests reached"¶
The model spent all 100 (MAX_TOOL_REQUEST_TIMES) tool-use rounds
without calling task_done. Comments emitted up to that point are
still collected and rendered. If this happens on most groups, the issue
is usually one of:
- Model isn't great at following the "call
task_donewhen finished" instruction. Switch to a stronger model (e.g., Claude Opus). - A tool keeps erroring and the model keeps retrying. Look at the session JSONL — if the same tool result repeats, that's why.
- The group is genuinely large or context-heavy and 100 rounds isn't
enough. Raise the cap with
--max-tools <n>(e.g.,--max-tools 150). The flag only ever raises the limit — a value below the template default of100is ignored, values 1–49 are clamped up to50, and0keeps the template default. - The model does not support native tool calling at all (common with local models) — see "No tool calls parsed" (local models / Ollama).
Some sub-agents fail; the run still exits 0¶
By design. kaid isolates per-group failures so one bad group doesn't kill
a 20-file review. The aggregate exit code is 0 if anything
succeeded; only a fully-failed run (zero successful sub-agents) exits
non-zero. Check the warnings array in JSON mode or stderr in text
mode to see which groups failed.
CI run is much slower than local¶
Two usual suspects:
- Model rate limits — under throttling, the LLM client backs off
and retries. Lower
--concurrency(e.g., to4) so you don't hit the limit in the first place. - Cold cache — if your provider supports prompt caching, the first run after deploy doesn't benefit from it. Subsequent runs in the same window are faster.
Output & integration¶
--audience agent still has progress lines¶
Make sure you're not seeing stderr. Progress messages occasionally
go to stderr (warnings, errors). The clean stdout that --audience
agent guarantees is parser-friendly — to suppress everything,
redirect: kaid review --audience agent 2>/dev/null.
JSON output is { "files_reviewed": 0, "comments": [] }¶
Nothing was eligible to review. files_reviewed is not a top-level
field — it sits under summary, where it reads 0 on this path, while
comments is the top-level []. The same object carries
"status": "skipped", "message": "Review skipped: no items were
selected.", and a manifest whose terminal_state is "skipped" with
every coverage array empty.
A review that examined files and found nothing also returns a JSON
object with comments: []: summary.files_reviewed counts the files
actually reviewed, status is "complete", and message reads
"Review complete: 0 finding(s) across N selected item(s).". Optional
top-level fields can differ between runs, so do not distinguish these
states by object shape or by the presence of an optional key. On
manifest-backed review output, use summary.files_reviewed or
manifest.terminal_state instead. Callers must still tolerate both
summary and manifest being absent on the manifest-less path described
below. review --format json always writes exactly one JSON object to
stdout, never a bare array.
The manifest-less no-files path is leaner: it omits both summary and
manifest, while still reporting "status": "skipped", "message": "No
supported files changed.", and "comments": []. tool_calls is always
present. kaid scan is always manifest-less; when its no-files guard
matches it uses this path. kaid review can also reach the same path when
manifest construction fails and the no-files guard matches. Optional
metadata such as llm or trace_id may be present.
Where do session JSONLs live?¶
The repo path is encoded by replacing / and \ with - and : with
_ (e.g. /Users/foo/my-repo → Users-foo-my-repo). Browse sessions
with kaid viewer. Delete the directory to wipe history; kaid regenerates
the encoded path on the next run.
Performance & cost¶
How can I tell what tokens cost what?¶
Enable telemetry:
In the main review loop, LLM calls produce llm.request spans and are
also recorded as metrics. Watch kaid.llm.tokens_used (counter, labelled model +
type), kaid.llm.requests_total (counter, labelled model +
status), and kaid.llm.request_duration_seconds (histogram, labelled
model). The console exporter prints these aggregates inline. For
dashboards, switch to the OTLP exporter and ship to your metrics
stack — see Telemetry.
Why are my reviews so expensive?¶
Common levers:
- The effort preset controls how many review rounds each group gets:
low= 1,medium(the default) = 2,high= 3. Cost scales roughly with the round count, so--effort lowis the single biggest lever if you want a cheaper run;--effort highis the most expensive. - Plan phase is on for groups whose largest file is ≥ 50 lines, or whose
2+ files total ≥ 100 lines. It costs an extra LLM call per group, so
raising those thresholds is what makes a run cheaper; lowering
them sends more groups through planning and costs more. The two do not
behave alike at zero.
PLAN_MODE_LINE_THRESHOLDat0or below means always plan — the dearest setting available — whereasPLAN_MODE_GROUP_LINE_THRESHOLDat0turns the group gate off. That can save the plan call when the group gate would otherwise be the only trigger. See "Plan phase took forever and the file is small" above for the trigger rules. MAX_TOOL_REQUEST_TIMES = 100is generous. A model that uses every round will produce a longer (more tokens) conversation than one that finishes in 3 rounds. Stronger models tend to finish faster. Conversely, if you raised it with--max-toolsto fight "max tool requests reached", expect cost per group to grow roughly linearly.- Memory compression itself is an LLM call. Long subtasks pay for compression rounds in addition to review rounds.
- Semantic grouping adds one small LLM call per run. It only sees file metadata (paths, status, insertion/deletion counts) — never diff content — so it is cheap, and it usually pays for itself by reviewing related files together instead of once per file.
How do I reduce LLM calls?¶
- Add an
includelist so kaid doesn't review files you don't care about. - Set
--concurrencylower if your account has burst-mode pricing. - Pass
--background— better context up-front sometimes lets the model finish withoutfile_read/code_searchround-trips.
Privacy & security¶
Does kaid send my code anywhere?¶
kaid sends your diffs (and optional read-tool snippets) to whatever LLM endpoint you configured. Nothing else leaves your machine — session JSONLs and rule files are local-only.
If telemetry is enabled, the content_logging flag is plumbed through
the config layer but currently gates no code path — prompt and
response content is never exported to your collector regardless of the
flag's value. Treat it as reserved. Leave it false in production. See
Telemetry for details.
Can I redact secrets before they're sent to the LLM?¶
Not built-in. The recommended workflow:
- Don't commit secrets to your repo (the usual rule).
- Add files known to contain hash material to
exclude. - Use
git diff --no-textconvfilters or pre-commit redaction to keep secrets out of diffs.
A "redaction rule" feature is on the roadmap; track the issue tracker.
Misc¶
Where's the changelog?¶
GitHub Releases — each release has notes generated from Conventional Commits.
Does kaid support non-Git VCS?¶
No. The diff providers shell out to git. SVN / Mercurial / etc. would
need new providers; an issue for Hg support is open
here.
Why is the binary called kaid but the CLI is kaid?¶
The static binary published in releases is named after the project
(kaid); the install script places it as kaid for
ergonomics. If you build from source you get dist/kaid
(dist/kaid.exe on Windows) —
copy it to kaid on your $PATH.
How do I uninstall?¶
kaid doesn't write outside ~/.kaid (apart from the binary
download via the install script), so removing that directory wipes history, config,
and per-user rules.
See Also¶
- Configuration - LLM endpoint resolution and config keys.
- Review Rules - the file filter and rule resolution chain.
- Session Viewer - inspect past review sessions.
- Telemetry - token usage and LLM metrics.