Skip to content

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:

  1. Trigger on a PR / MR event. A new pull request, an updated merge request, or a manual /kaid comment kicks off the job.
  2. Install kaid in the runner, typically curl -fsSL https://raw.githubusercontent.com/CHE3MZ/kaid/master/scripts/install.sh | sh. The runner is ephemeral, so this happens on every run.
  3. Configure the LLM from CI secrets via kaid config set (endpoint, token, model). There is no persisted ~/.kaid to fall back on.
  4. 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) and issue_comment events whose body starts with /kaid or @kaid — the latter lets reviewers re-run kaid on demand by commenting on a PR. (pull_request_target is used instead of pull_request so 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 with kaid 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):

on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]

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.

  1. 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:
  2. Pull requests: Read and write
  3. Contents: Read-only (for fetching diffs)
  4. Metadata: Read-only (required)

  5. Generate a private key from the app settings page and download the .pem file. Note the App ID from the same page.

  6. 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 is 12345.

  7. 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.
  1. 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_DEBUG env var is not currently implemented in kaid — setting KAID_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.json and /tmp/kaid-stderr.log (see troubleshooting below), or run kaid review locally.

GitLab CI

The upstream pipeline lives at examples/gitlab_ci/.gitlab-ci.yml.

What it does

  • Triggers on merge_requests events (all MR events — creation, updates, reopen).
  • Runs in a node:20 image, installs kaid, configures it via kaid 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 versions endpoint to compute correct base_sha / start_sha / head_sha for 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::

include:
  - local: 'ci/kaid-review.gitlab-ci.yml'

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_anthropic is hardcoded to false in 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 Bot is 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.

  1. 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.

  2. Invite it to the project at Settings → Members → Invite member. Search for the service-account name and assign Developer or Maintainer — both have the permissions needed to post discussions.

  3. 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.

  4. Swap the token value at Settings → CI/CD → Variables — replace the existing GITLAB_API_TOKEN value 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:

script:
  - cat /tmp/kaid-result.json
  - cat /tmp/kaid-stderr.log

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.