CI/CD
Run kaid on every Pull Request or Merge Request. The upstream repo ships two ready-made pipelines you copy and configure — one for GitHub Actions, one for GitLab CI. Both are thin wrappers around the core command documented in the CLI Reference.
How CI/CD integration works¶
Every recipe on this page follows the same pattern — the GitHub Actions and GitLab CI sections below are just the concrete implementations of it:
- Trigger on a PR / MR event. A new pull request, an updated
merge request, or a manual
/kaidcomment kicks off the job. - Install
kaidin the runner, typicallycurl -fsSL https://raw.githubusercontent.com/CHE3MZ/kaid/master/scripts/install.sh | sh. The runner is ephemeral, so this happens on every run. - Configure the LLM from CI secrets via
kaid config set(endpoint, token, model). There is no persisted~/.kaidto fall back on. - Run the review in range mode with machine-readable output, so stdout is a clean JSON envelope:
kaid review \
--from "origin/<base-branch>" \
--to "origin/<head-branch>" \
--format json \
--audience agent
--format json gives a parseable payload; --audience agent
suppresses progress lines. See the
JSON output for the envelope every
recipe consumes.
5. Parse the JSON and walk comments[].
6. Post comments back to the PR / MR via the provider's review
API. Entries without valid line info (file-level findings) are
folded into a summary note instead of being posted inline; the
posting step also falls back to a plain summary comment if the
inline-batch API rejects the request.
sequenceDiagram
autonumber
participant CI as CI runner
participant kaid as kaid CLI
participant LLM as LLM endpoint
participant SCM as GitHub / GitLab
CI->>kaid: install and configure
CI->>kaid: kaid review --format json --audience agent
kaid->>LLM: review requests
LLM-->>kaid: findings
kaid-->>CI: JSON envelope
CI->>SCM: post inline review comments
Two credential types
The LLM credentials let kaid generate findings. The PR/MR write
token lets the posting step comment back. The GitHub recipe gets the
latter for free via GITHUB_TOKEN; GitLab recommends an explicit
GITLAB_API_TOKEN, but the built-in CI_JOB_TOKEN is used as a fallback
for fork MRs (it can post discussions via /discussions) - a dedicated
token is recommended for reliability.
GitHub Actions¶
The upstream workflow lives at
examples/github_actions/kaid-review.yml.
What it does¶
- Triggers on
pull_request_target(opened) andissue_commentevents whose body starts with/kaidor@kaid— the latter lets reviewers re-run kaid on demand by commenting on a PR. (pull_request_targetis used instead ofpull_requestso that secrets are available even for PRs opened from forks; kaid only reads the diff and does not execute code from the PR.) - Installs kaid via
curl -fsSL https://raw.githubusercontent.com/CHE3MZ/kaid/master/scripts/install.sh | sh, writes config withkaid config set, then runs the core command in branch-range mode. - Parses the JSON envelope and posts each finding as an inline review comment via the GitHub Pull Request Review API. Comments without line info are folded into the summary body. If batch submission fails, it falls back to posting comments one-by-one and surfaces statistics in a summary comment.
Install¶
Drop the workflow into your repo:
mkdir -p .github/workflows
curl -o .github/workflows/kaid-review.yml \
https://raw.githubusercontent.com/CHE3MZ/kaid/master/examples/github_actions/kaid-review.yml
Required secrets¶
Set under Settings → Secrets and variables → Actions:
| Secret | Required | Description |
|---|---|---|
KAID_LLM_URL |
Yes | LLM API endpoint (e.g. https://api.openai.com/v1/chat/completions). |
KAID_LLM_AUTH_TOKEN |
Yes | Authentication token for the LLM API. This CI secret is passed to kaid config set llm.auth_token. (kaid's direct env var is KAID_LLM_TOKEN, not KAID_LLM_AUTH_TOKEN.) |
KAID_LLM_MODEL |
No | Model name. No default — must be set explicitly. |
KAID_LLM_USE_ANTHROPIC |
No | Set to true for Anthropic Claude models. |
GITHUB_TOKEN is auto-provided; the workflow declares
pull-requests: write so it can post review comments.
Thinking mode is disabled by default
The workflow also runs
kaid config set llm.extra_body '{"thinking": {"type": "disabled"}}' at
startup, which turns off thinking-mode requests for compatibility across
LLM providers that don't support that field. Remove the line if your
provider needs thinking mode left on.
Action inputs¶
The upstream workflow delegates the review to the reusable composite action
(action.yml)
via uses: CHE3MZ/kaid@master. Beyond the credentials above,
these inputs tune the review itself — pass them under with: on the action
step:
| Input | Default | Description |
|---|---|---|
effort |
'' |
Review effort preset passed to kaid review --effort: low, medium, or high (case-insensitive). Empty keeps the CLI default (the configured value, or medium). Requires kaid v1.10.0 or newer; the action fails early with a clear error on older versions. |
max_tokens_budget |
'' |
Total token cap (input + output) passed to kaid review --max-tokens-budget. Empty or '0' means unlimited. Checked before every LLM round: a subtask already over the cap gets one final round to submit findings, no further subtasks are dispatched, over-budget and skipped files are reported as failed(budget), partial results are still published, and the review exits 0. |
llm_reasoning_effort |
'' |
Reasoning depth for models with a steerable reasoning_effort request field (e.g. GLM-5.x, OpenAI reasoning models): minimal, low, medium, high, max (case-insensitive). Merged into the request body through llm_extra_body, so it works with every published CLI version; an explicit reasoning_effort key in llm_extra_body wins over this input. Empty (default) sends nothing. OpenAI-compatible protocols only — the Anthropic API rejects unknown body fields, so the action fails fast on that protocol; steer Anthropic thinking through an explicit llm_extra_body key instead. |
stream_progress |
'false' |
'true' streams live [kaid] progress lines to the workflow log (human audience on stderr) instead of staying silent until the run finishes. Display-only: stderr is still captured to a file for artifacts and comment posting. |
- uses: CHE3MZ/kaid@master
with:
llm_url: ${{ secrets.KAID_LLM_URL }}
llm_auth_token: ${{ secrets.KAID_LLM_AUTH_TOKEN }}
llm_model: ${{ vars.KAID_LLM_MODEL }}
llm_use_anthropic: ${{ vars.KAID_LLM_USE_ANTHROPIC }}
effort: high
max_tokens_budget: '10000000'
llm_reasoning_effort: low
stream_progress: 'true'
See action.yml
for the full input list — posting modes (sticky_summary, incremental),
severity/category routing, and cross-push checkpoints included.
Customization¶
All of the following are edits to the workflow file you just copied
(.github/workflows/kaid-review.yml).
Background context¶
--background is the single highest-leverage flag — see the
tips that apply to every pattern.
Feed the PR title (works especially well when titles follow a
semantic convention like feat(auth): add OAuth2 support):
- name: Run kaid review
env:
PR_TITLE: ${{ github.event.pull_request.title }}
BASE_REF: ${{ github.base_ref }}
HEAD_REF: ${{ github.head_ref }}
run: |
kaid review \
--background "$PR_TITLE" \
--from "origin/$BASE_REF" \
--to "origin/$HEAD_REF" \
--format json --audience agent
Pass PR-controlled values through env: rather than
interpolating ${{ }} directly into run:. GitHub substitutes
${{ }} textually before the shell parses the line, so a PR
title or branch name containing shell metacharacters would
execute on your runner.
Custom rules¶
Pass a project-specific rule file with --rule:
- name: Run kaid review
env:
BASE_REF: ${{ github.base_ref }}
HEAD_REF: ${{ github.head_ref }}
run: |
kaid review --rule ./my-rules.json \
--from "origin/$BASE_REF" \
--to "origin/$HEAD_REF"
See Review Rules for the schema.
Concurrency¶
The default is 8 parallel sub-agents, one per subtask. Lower it on large PRs to stay under your LLM provider's rate limits:
- name: Run kaid review
env:
BASE_REF: ${{ github.base_ref }}
HEAD_REF: ${{ github.head_ref }}
run: |
kaid review --concurrency 5 \
--from "origin/$BASE_REF" \
--to "origin/$HEAD_REF"
Trigger pattern¶
The default workflow triggers on PR opened and on PR comments
beginning with /kaid or @kaid. Two common
adjustments:
Run on more PR lifecycle events (e.g., re-review when new commits are pushed):
Use a different comment keyword:
if: |
github.event_name == 'pull_request' ||
(github.event_name == 'issue_comment'
&& github.event.issue.pull_request
&& startsWith(github.event.comment.body, '/review'))
The github.event.issue.pull_request check ensures the comment is
on a PR, not a regular issue.
Pin the kaid version¶
The default workflow installs the latest published version. To pin:
- name: Install kaid
run: curl -fsSL https://raw.githubusercontent.com/CHE3MZ/kaid/master/scripts/install.sh | KAID_VERSION=1.0.0 sh
Post under a GitHub App identity¶
By default, review comments come from github-actions[bot]. To post
under a branded bot like kaid Bot, swap GITHUB_TOKEN
for a GitHub App installation token.
- Create the app at Settings → Developer settings → GitHub Apps → New GitHub App. Disable the webhook (not needed for this use case). Under Repository permissions grant:
- Pull requests: Read and write
- Contents: Read-only (for fetching diffs)
-
Metadata: Read-only (required)
-
Generate a private key from the app settings page and download the
.pemfile. Note the App ID from the same page. -
Install the app on the repositories you want kaid to review. The Installation ID appears in the post-install URL, e.g.
https://github.com/settings/installations/12345→ ID is12345. -
Add three secrets under Settings → Secrets and variables → Actions:
| Secret | Value |
|---|---|
GITHUB_APP_ID |
The App ID. |
GITHUB_APP_PRIVATE_KEY |
Full contents of the .pem file, including the -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY----- lines. |
GITHUB_APP_INSTALLATION_ID |
The Installation ID. |
- Mint a token and use it in the comment-posting step:
- name: Get GitHub App Token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.GITHUB_APP_ID }}
private-key: ${{ secrets.GITHUB_APP_PRIVATE_KEY }}
- name: Post review comments to PR
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
# ...existing post script...
Reviews will now appear as posted by your app's name instead of
github-actions[bot].
Upload findings to GitHub Code Scanning (SARIF)¶
--format sarif writes a
SARIF 2.1.0
report to stdout. Pipe it to a file and upload it with the CodeQL
upload-sarif action so findings appear under
Security → Code scanning:
- name: Run kaid review
env:
BASE_REF: ${{ github.base_ref }}
HEAD_REF: ${{ github.head_ref }}
run: |
kaid review \
--from "origin/$BASE_REF" \
--to "origin/$HEAD_REF" \
--format sarif --audience agent > results.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
SARIF is machine-readable, so kaid suppresses progress lines on stdout
and results.sarif contains only the report. --preview does not
support --format sarif — run a full review (or kaid scan) to
produce a report.
Troubleshooting¶
| Symptom | Cause / Fix |
|---|---|
Cannot find merge-base |
The checkout step used a shallow clone, but range-mode review needs full history. The upstream workflow sets fetch-depth: 0 on actions/checkout — preserve that setting if you edit the file. |
Failed to parse kaid output |
KAID_LLM_URL or KAID_LLM_AUTH_TOKEN is missing or wrong. Re-check the values under Settings → Secrets and variables → Actions. |
| Review comments land on the wrong lines | Usually means the diff shifted between the moment the review started and when comments were posted. The posting script falls back to a plain issue comment in that case — no action needed. |
Note. The
KAID_DEBUGenv var is not currently implemented in kaid — settingKAID_DEBUG: "1"has no effect. It's documented here in case it is wired up later. For verbose output today, inspect the raw review JSON and stderr that the workflow writes to/tmp/kaid-result.jsonand/tmp/kaid-stderr.log(see troubleshooting below), or runkaid reviewlocally.
GitLab CI¶
The upstream pipeline lives at
examples/gitlab_ci/.gitlab-ci.yml.
What it does¶
- Triggers on
merge_requestsevents (all MR events — creation, updates, reopen). - Runs in a
node:20image, installs kaid, configures it viakaid config set, then runs the core command in MR diff mode. - Parses the JSON envelope with an inlined Python script and posts
each finding as a GitLab Discussion (inline on the diff), using
the MR's
versionsendpoint to compute correctbase_sha/start_sha/head_shafor accurate positioning. Falls back to regular MR notes for any comment that can't be posted inline, and closes with a summary note.
Install¶
Drop the pipeline into your repo root:
curl -o .gitlab-ci.yml \
https://raw.githubusercontent.com/CHE3MZ/kaid/master/examples/gitlab_ci/.gitlab-ci.yml
If you already have a .gitlab-ci.yml and want to keep it, vendor
the recipe to a different path and pull it in with include::
Required CI/CD variables¶
Set under Settings → CI/CD → Variables:
| Variable | Required | Masked | Description |
|---|---|---|---|
KAID_LLM_URL |
Yes | No | LLM API endpoint URL. |
KAID_LLM_AUTH_TOKEN |
Yes | Yes | API authentication token. This CI variable is passed to kaid config set llm.auth_token. (kaid's direct env var is KAID_LLM_TOKEN, not KAID_LLM_AUTH_TOKEN.) |
KAID_LLM_MODEL |
No | No | Model name. No default — must be set explicitly. |
GITLAB_API_TOKEN |
No | Yes | Project / personal / group access token with api scope. Optional — the built-in CI_JOB_TOKEN is used as a fallback when this is absent (e.g. for fork MRs). A dedicated GITLAB_API_TOKEN is recommended for reliability. |
GitLab rejects variables shorter than 8 characters, so
llm.use_anthropicis hardcoded tofalsein the pipeline. To use Anthropic Claude models, edit the script directly.The pipeline also runs
kaid config set llm.extra_body '{"thinking": {"type": "disabled"}}'at startup, which turns off thinking-mode requests for compatibility across LLM providers that don't support that field. Remove the line if your provider needs thinking-mode left on.Quick bot-naming tip. For Project Access Tokens and Group Access Tokens, the token's name is what appears next to MR discussions. Naming the token
kaid Botis a fast way to brand the reviewer without setting up anything else — handy when you don't need the more durable service-account setup documented under Post under a service account identity.
Customization¶
All of the following are edits to the .gitlab-ci.yml you just
copied.
Background context¶
Pass the MR title to --background — especially useful when titles
follow a semantic convention like feat(auth): add OAuth2 support:
script:
- |
kaid review \
--background "$CI_MERGE_REQUEST_TITLE" \
--from "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" \
--to "${CI_COMMIT_SHA}" \
--format json --audience agent
Custom rules and concurrency¶
Same flags as the GitHub Actions recipe — pass --rule for a
project-specific rule file, and --concurrency to throttle parallel
sub-agents (default 8, one per subtask):
script:
- |
kaid review --rule ./my-rules.json --concurrency 5 \
--from "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" \
--to "${CI_COMMIT_SHA}"
See Review Rules for the rule schema.
Pin the kaid version¶
script:
- curl -fsSL https://raw.githubusercontent.com/CHE3MZ/kaid/master/scripts/install.sh | KAID_VERSION=1.0.0 sh
Avoid re-reviewing on every push¶
only: [merge_requests] triggers on every MR update, which can
burn a lot of LLM tokens on long-running MRs. GitLab has no native
"only on creation" event, so the recommended pattern is to detect
existing kaid notes before running the review and bail out if any are
found. Replace the kaid review invocation with a Python wrapper:
import json, os, sys, urllib.request
GITLAB_URL = os.environ.get("CI_SERVER_URL", "https://gitlab.com")
PROJECT_ID = os.environ["CI_PROJECT_ID"]
MR_IID = os.environ["CI_MERGE_REQUEST_IID"]
API_TOKEN = os.environ["GITLAB_API_TOKEN"]
url = (
f"{GITLAB_URL}/api/v4/projects/{PROJECT_ID}"
f"/merge_requests/{MR_IID}/notes?per_page=100"
)
req = urllib.request.Request(url, headers={"PRIVATE-TOKEN": API_TOKEN})
with urllib.request.urlopen(req) as resp:
notes = json.loads(resp.read().decode())
if any("kaid" in n.get("body", "") for n in notes):
print("kaid already reviewed this MR. Skipping to save tokens.")
sys.exit(0)
# ...otherwise call `kaid review ...` as usual and write the JSON to
# the file the posting step expects.
To force a re-review after this, delete the previous kaid notes from the MR — the next pipeline run will see no kaid notes and proceed.
Self-hosted GitLab¶
No code change needed. The posting script reads CI_SERVER_URL
(which GitLab sets automatically on every runner), so it talks to
your own instance out of the box. Just make sure
GITLAB_API_TOKEN is issued by your self-hosted instance, not
gitlab.com.
Post under a service account identity¶
By default, review discussions appear under whichever user owns
GITLAB_API_TOKEN. Swap in a project-scoped service account for a
branded bot identity like kaid Bot.
-
Create the service account at Project → Settings → Service Accounts → New service account. The name you pick (e.g.
kaid Bot) is what appears next to MR discussions. -
Invite it to the project at Settings → Members → Invite member. Search for the service-account name and assign
DeveloperorMaintainer— both have the permissions needed to post discussions. -
Issue an access token at Settings → Service Accounts → (the account) → Add new token. Required scope:
api. Copy the token immediately — GitLab only shows it once. -
Swap the token value at Settings → CI/CD → Variables — replace the existing
GITLAB_API_TOKENvalue with the service account's token (keep the variable name the same).
Discussions are now posted under the service account name instead of the user who originally created the token.
Troubleshooting¶
| Symptom | Cause / Fix |
|---|---|
Cannot find merge-base |
The runner used a shallow clone. The upstream pipeline sets GIT_DEPTH: 0 to force a full clone — preserve that setting if you edit the file. |
API error 403 when posting |
GITLAB_API_TOKEN is missing the api scope, isn't a member of the project, or — on self-hosted — was issued by a different instance. Reissue with api scope and re-add it under Settings → CI/CD → Variables. |
Failed to parse kaid output |
KAID_LLM_URL or KAID_LLM_AUTH_TOKEN is wrong. Re-check the values under Settings → CI/CD → Variables. |
| Inline comments land on the wrong lines | GitLab requires exact SHA matching for inline discussions; the posting script fetches versions metadata to get the right base_sha / start_sha / head_sha. If a finding still can't be anchored, it falls back to a plain MR note. |
The pipeline writes raw review JSON to /tmp/kaid-result.json and
stderr to /tmp/kaid-stderr.log. Cat them in a debug step to inspect
what kaid returned:
See Also¶
- CLI Reference - the JSON output shape both pipelines consume, useful when writing your own CI script from scratch.
- Configuration - every env var and config key kaid honors.