One action, two modes: architecture review on every pull request, and a versioned, always-current architecture baseline on your main branch.
mode: review(the default) — CodeBoarding analyzes your architecture on the target branch and PR branch, comments on the PR with an inline Mermaid diagram and hosted webview link, and uploads the PR-headanalysis.jsonplus target-branch metadata as a GitHub Actions artifact. It never commits generated files to the PR branch. Runs onpull_requestandissue_comment.mode: sync— CodeBoarding keeps your architecture analysis versioned and current on your branch: on every push it commits theanalysis.jsonbaseline,static_analysis.pklcache pair, health report, and readable markdown (.codeboarding/*.md), so reviews diff against your current architecture and your architecture has real git history. Runs onpush,workflow_dispatch, andschedule. See sync mode.
Both modes run the CodeBoarding engine in CI: static analysis combined with LLM reasoning. They are designed to be used together — sync mode keeps the baseline fresh that review mode diffs against — but each works on its own.
CodeBoarding · Website · Explore examples · VS Code extension · Discord
- Builds or reuses a baseline architecture analysis for the target branch tip the PR is opened against.
- Runs incremental analysis on the PR head, then diffs components and relationships.
- Posts a sticky PR comment with an inline Mermaid map. Green is added, yellow is modified, red (dashed) is deleted, for both nodes and edges.
- Uploads the PR-head
analysis.jsonplus target-branch metadata as a GitHub Actions artifact and links the hosted webview to that artifact instead of committing generated files to the PR branch.
A PR comment looks like this:
graph LR
Orchestration_Workflow_Manager["Orchestration & Workflow Manager"]
Incremental_Analysis_Controller["Incremental Analysis Controller"]
Static_Analysis_Engine["Static Analysis Engine"]
Agentic_Intelligence_Core["Agentic Intelligence Core"]
Health_Quality_Monitor["Health & Quality Monitor"]
Rendering_Output_Engine["Rendering & Output Engine"]
Persistence_Provider_Infrastructure["Persistence & Provider Infrastructure"]
Orchestration_Workflow_Manager -- "triggers change detection" --> Incremental_Analysis_Controller
Incremental_Analysis_Controller -- "passes filtered file sets" --> Static_Analysis_Engine
Static_Analysis_Engine -- "provides CFGs and symbol tables" --> Agentic_Intelligence_Core
Static_Analysis_Engine -- "supplies structural metrics" --> Health_Quality_Monitor
Agentic_Intelligence_Core -- "delivers summaries and diagrams" --> Rendering_Output_Engine
Health_Quality_Monitor -- "provides health reports" --> Rendering_Output_Engine
Persistence_Provider_Infrastructure -- "supplies LLM clients" --> Agentic_Intelligence_Core
Orchestration_Workflow_Manager -- "persists pipeline state" --> Persistence_Provider_Infrastructure
classDef added fill:#1f883d,stroke:#0b5d23,color:#fff;
classDef modified fill:#bf8700,stroke:#7d4e00,color:#fff;
classDef deleted fill:#cf222e,stroke:#82071e,color:#fff,stroke-dasharray:5 3;
class Health_Quality_Monitor added;
class Static_Analysis_Engine,Agentic_Intelligence_Core modified;
class Persistence_Provider_Infrastructure deleted;
linkStyle 3,5 stroke:#1f883d,stroke-width:2px;
linkStyle 2 stroke:#bf8700,stroke-width:2px;
linkStyle 6,7 stroke:#cf222e,stroke-width:2px,stroke-dasharray:5 3;
Create .github/workflows/codeboarding.yml:
name: CodeBoarding review
on:
pull_request:
# Generate once, when the PR becomes reviewable, not on every push, so you
# don't spend an LLM job per commit. Use [opened] for strictly creation-only,
# or add `synchronize` to re-run on each push. Refresh anytime with /codeboarding.
# 'closed' only cancels an in-flight review (see concurrency), it doesn't start one.
types: [opened, reopened, ready_for_review, closed]
issue_comment:
types: [created]
permissions:
contents: read
pull-requests: write
issues: write
# Lets the action mint a short-lived GitHub OIDC token so the free hosted tier
# can identify your repository. Required for the no-secret (free-tier) path;
# harmless to keep when you bring your own key.
id-token: write
concurrency:
group: codeboarding-${{ github.event.pull_request.number || github.event.issue.number }}
# Cancel only when the PR closes — bot comments (issue_comment) and re-triggers
# must not cancel a running review; they queue behind it instead.
cancel-in-progress: ${{ github.event_name == 'pull_request' && github.event.action == 'closed' }}
jobs:
review:
runs-on: ubuntu-latest
timeout-minutes: 60
if: >
(github.event_name == 'pull_request' && github.event.action != 'closed' && github.event.pull_request.draft == false) ||
(github.event_name == 'issue_comment' && github.event.issue.pull_request != null &&
startsWith(github.event.comment.body, '/codeboarding') &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association))
steps:
- uses: CodeBoarding/CodeBoarding-action@v1That's it — no extra setup. With id-token: write granted, the action runs on the free hosted tier: it mints a GitHub OIDC token, and CodeBoarding's proxy supplies the LLM, metered per repository owner against a weekly cap. Merge the workflow and your next pull request gets an architecture diff.
Models are optional. Omit agent_model and parsing_model to use the defaults, or pin them inline or from a repository variable (a model name is not a secret, so use vars., not secrets.):
with:
agent_model: anthropic/claude-sonnet-4 # optional; or ${{ vars.AGENT_MODEL }}
parsing_model: google/gemini-3-flash-preview # optionalThe free tier is metered per repository owner against a weekly cap. For more — or unmetered — usage, supply a credential. Both paths skip the proxy/OIDC and need no id-token: write:
with:
# Option A — your own OpenRouter key (talks to OpenRouter directly):
llm_api_key: ${{ secrets.OPENROUTER_API_KEY }}
# Option B — a CodeBoarding license (unmetered hosted usage):
# license_key: ${{ secrets.CODEBOARDING_LICENSE }}Add the secret under Settings → Secrets and variables → Actions (e.g. OPENROUTER_API_KEY = sk-or-...). For local runs with scripts/run_local.sh, export OPENROUTER_API_KEY as an environment variable instead. When llm_api_key is set it takes precedence; license_key is used only when no key is set; with neither, the free OIDC tier is used.
OpenRouter is the default, but you can use any provider the engine supports. Set llm_provider and pass that provider's key:
with:
llm_provider: anthropic # omit for OpenRouter (default)
llm_api_key: ${{ secrets.ANTHROPIC_API_KEY }}llm_provider: <name> hands your key to the engine as <NAME>_API_KEY, and the engine auto-selects that provider. Set exactly one key per run.
Supported providers
llm_provider |
Environment variable the engine reads |
|---|---|
openrouter (default) |
OPENROUTER_API_KEY |
openai |
OPENAI_API_KEY |
anthropic |
ANTHROPIC_API_KEY |
google |
GOOGLE_API_KEY |
vercel |
VERCEL_API_KEY |
deepseek |
DEEPSEEK_API_KEY |
cerebras |
CEREBRAS_API_KEY |
glm / kimi |
GLM_API_KEY / KIMI_API_KEY |
aws_bedrock |
AWS_BEARER_TOKEN_BEDROCK |
ollama |
OLLAMA_BASE_URL |
This table mirrors the engine and may lag it. The source of truth is the engine's provider registry, agents/llm_config.py. Any provider it adds that follows the <NAME>_API_KEY convention works here with no action change.
- On a PR being opened, reopened, or marked ready for review, the diagram is generated once (per the
on:triggers above). It does not re-run on every push, so you never spend an LLM job per commit; the comment reflects that point until refreshed. - On a
/codeboardingcomment, a trusted collaborator (OWNER,MEMBER, orCOLLABORATOR) regenerates the diagram against the current PR head, even if one already exists. Each/codeboardinginvocation posts a new comment and leaves earlier comments untouched (the automatic on-open comment, and any previous/codeboardingresults, stay put). Change the keyword viatrigger_command.
The command needs the issue_comment trigger and runs from your default branch (a GitHub rule), so it only works once the workflow is merged there. On-demand runs on fork PRs are refused, so fork code is never analyzed with your secrets.
In review workflows that include issue_comment, anyone whose comment reaches the action can send product feedback with:
/codeboarding-feedback <message>
With mode: sync, the action analyzes the pushed commit and commits the results back to the branch (as codeboarding[bot]), so your architecture analysis stays versioned in git and tracks the code instead of drifting from it:
.codeboarding/*.md— rendered architecture docs:overview.mdplus one page per component (directory configurable viaoutput_dir)..codeboarding/analysis.json— the machine-readable analysis, which doubles as the baseline that review mode diffs against..codeboarding/static_analysis.pkl+.codeboarding/static_analysis.sha— the static-analysis cache pair used to keep future incremental runs fast and reproducible..codeboarding/health/health_report.json— health findings for the committed baseline.docs/development/architecture.md(optional, on by default) — all pages concatenated into a single document,overview.mdfirst. Disable withwrite_architecture_md: false.
Create .github/workflows/codeboarding-sync.yml next to your review workflow:
name: CodeBoarding sync
on:
push:
branches: [main]
# Loop guard: don't re-trigger on the files this workflow itself commits.
# Listed explicitly (not '.codeboarding/**') so that editing your own
# .codeboarding/.codeboardingignore still regenerates the docs. (The action
# also skips re-analyzing its own bot commit as a backstop, and deliberately
# does NOT use [skip ci] — that would leak through squash-merges.)
paths-ignore:
- '.codeboarding/*.md'
- '.codeboarding/analysis.json'
- '.codeboarding/static_analysis.pkl'
- '.codeboarding/static_analysis.sha'
- '.codeboarding/codeboarding_version.json'
- '.codeboarding/health/**'
- 'docs/development/architecture.md'
workflow_dispatch:
permissions:
contents: write # commit the generated docs to the branch
id-token: write # free hosted tier (omit if you set llm_api_key/license_key)
concurrency:
group: codeboarding-sync
cancel-in-progress: false
jobs:
sync:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: CodeBoarding/CodeBoarding-action@v1
with:
mode: sync
# Runs on the free tier with no extra setup. For more/unmetered usage add
# `llm_api_key: ${{ secrets.OPENROUTER_API_KEY }}` (and drop id-token: write).Behavior worth knowing:
- The first run on a branch is a full analysis at depth 2 by default; subsequent runs reuse the committed baseline and run incrementally when they can (the
analysis_modeoutput tells you which happened). Once ananalysis.jsonexists, its recordedmetadata.depth_levelis preserved for incremental runs and fallback-full recovery. - The commit is skipped when nothing meaningful changed (an empty diff, or only
generated_at/timestamp fields). The push retries a few times with fetch+rebase and fails open, so a race with another push never fails your CI. - Tag pushes are skipped.
pull_requestevents soft-skip in sync mode, so a mistakenly shared workflow can never push docs from a PR run. - The bot commit carries no
[skip ci]— on a squash-merge that marker leaks into the merge commit and would skip the very sync run (and release tooling, CI) the merge should trigger. The regen loop is instead prevented by thepaths-ignorelist above and by the action skipping re-analysis of its own bot commit, so a merge tomainreliably triggers a fresh incremental sync. output_diris owned by the action: pre-existing top-level markdown files in it are deleted on every run (stale component pages must not linger). Don't point it at a directory with hand-written docs.
Sync mode keeps the committed .codeboarding/analysis.json baseline fresh on main. Review mode reuses that committed baseline from the target branch tip, so PR reviews diff against your current main architecture and run incrementally instead of rebuilding the target analysis from scratch — faster and cheaper per PR.
For fork PRs, review mode compares the PR branch against the fork's branch with the same name as the PR target branch. For example, a PR opened into upstream/main from alice:feature compares alice:main to alice:feature when alice:main exists. If the fork comparison branch has no committed .codeboarding/analysis.json, review mode uses an empty baseline and renders the PR architecture as newly added instead of silently comparing against upstream's baseline.
Leave depth_level empty unless you are choosing the depth for a first run or an intentional force_full rebuild. After a baseline exists, the committed analysis.json records the depth the engine should continue using, so review and sync mode do not need duplicate depth-selection logic.
Review mode never commits generated artifacts to PR branches, so squash merges do not orphan PR-head analysis.json files on main. Sync mode running on main is the only writer of the committed baseline.
Use two thin workflow files, each with least privilege, exactly as in the snippets above:
- review workflow —
on: pull_request(types[opened, reopened, ready_for_review]; the quick start addsclosedpurely to cancel in-flight runs) +issue_comment(types[created]);permissions: contents: read, pull-requests: write, issues: write. - sync workflow —
on: push(branches[main], with thepaths-ignorelist) +workflow_dispatch;permissions: contents: write.
The anti-pattern to avoid: one workflow with on: [push, pull_request] and a single union permissions block — it forces every privilege either mode needs onto every trigger. Sync mode soft-skips on pull_request events as a backstop, but don't rely on it: keep the triggers and permissions split so each workflow grants only what its own mode uses.
Review mode does not need contents: write: PR-specific generated files are stored as workflow artifacts. Only sync mode pushes generated architecture state back to git.
| Input | Mode | Default | Description |
|---|---|---|---|
llm_api_key |
both | empty | Your LLM provider API key (see llm_provider). Leave empty to use the free hosted tier via a GitHub OIDC token (needs permissions: id-token: write). |
llm_provider |
both | openrouter |
Provider for the key, mapped to <NAME>_API_KEY (e.g. anthropic, openai, google). Ignored on the free/license hosted tier (always OpenRouter via the proxy). |
license_key |
both | empty | A CodeBoarding license for unmetered hosted usage. Used when llm_api_key is empty; takes precedence over the free tier. |
proxy_url |
both | CodeBoarding proxy | Hosted LLM proxy base URL for the free/license tiers (the engine's OPENROUTER_BASE_URL). Override only for a self-hosted/dev proxy. |
mode |
both | review |
review posts the PR architecture-diff comment; sync analyzes on push and commits the architecture (analysis.json + rendered docs) to target_branch, keeping it versioned and current. |
github_token |
both | ${{ github.token }} |
Token for GitHub API calls; in review mode it posts or updates the PR comment. |
push_token |
sync | ${{ github.token }} |
Token used for sync-mode pushes to target_branch. The workflow token can push when the workflow grants permissions: contents: write. Separate from github_token so commenting can use a GitHub App token while the push uses the workflow token. |
codeboarding_version |
both | 0.12.3 |
CodeBoarding PyPI package version used as the analysis engine. Pin for reproducibility. |
depth_level |
both | empty (2 for cold starts) |
Analysis depth, 1 to 3, used for first analysis and force_full rebuilds. Once .codeboarding/analysis.json exists, its metadata.depth_level is the source of truth for incremental analysis and fallback-full recovery. |
render_depth |
review | 1 |
Display depth for the PR diagram. Keep 1 for a clean top-level view. |
diagram_direction |
review | LR |
Mermaid direction: LR, TD, TB, RL, or BT. |
changed_only |
review | false |
Render only changed components and incident edges. |
agent_model |
both | google/gemini-3-flash-preview |
Analysis model. OpenRouter default shown; other providers use their own engine default. |
parsing_model |
both | google/gemini-3.1-flash-lite-preview |
Parsing model. OpenRouter default shown; other providers use their own engine default. |
comment_header |
review | Architecture review |
Heading for the PR comment. |
trigger_command |
review | /codeboarding |
Slash command for trusted on-demand runs. |
cta_base_url |
review | empty | Click-proxy base URL: deep-links the editor link into VS Code/Cursor and adds a "get the extension" link (tracks owner/repo/pr). Empty links to the extension listing instead (GitHub strips vscode:/cursor: from comments). |
webview_base_url |
review | https://app.codeboarding.org |
Hosted webview base URL. The PR comment links to an artifact-backed head-vs-comparison-branch architecture diff. Set empty to disable the browser link. |
output_dir |
sync | .codeboarding |
Directory the rendered docs and analysis metadata are committed to. Owned by the action: pre-existing top-level .md files in it are deleted on every run. |
output_format |
sync | .md |
Output format. Only .md is supported. |
target_branch |
sync | ${{ github.ref_name }} |
Branch the generated docs are pushed to. |
write_architecture_md |
sync | true |
Also write docs/development/architecture.md: all rendered pages concatenated, overview.md first. |
commit_message |
sync | chore(codeboarding): sync architecture baseline |
Commit message for the generated docs. No [skip ci] (it would leak through squash-merges); the regen loop is guarded by paths-ignore + the action's own bot-commit check. |
force_full |
sync | false |
Ignore any committed baseline and run a full analysis from scratch. Use to rebuild a stale or corrupt baseline (e.g. from a workflow_dispatch). |
| Output | Mode | Description |
|---|---|---|
diagram_md |
review | Path to the generated Mermaid markdown block on the runner. |
n_changed |
review | Number of changed components, counted recursively. |
truncated |
review | true when the graph was reduced to fit GitHub Mermaid limits. |
review_artifact_url |
review | GitHub Actions artifact URL containing the PR-head analysis.json and comparison-branch metadata. |
analysis_mode |
sync | full or incremental: whether the run rebuilt the analysis from scratch or reused the committed baseline. |
files_written |
sync | The generated files written for the docs commit. |
committed |
sync | true when a docs commit was pushed to target_branch; false when sync mode ran but had nothing to commit (or the push failed open). Empty only if sync mode did not run. |
Outputs of the mode that did not run are empty strings.
- No checkout step is required in your workflow. This action checks out the target (the PR in review mode, the pushed commit in sync mode) and installs the CodeBoarding engine from PyPI internally.
- GitHub withholds secrets from fork PRs on
pull_request, so fork runs fail early if an LLM key is unavailable. - Do not use
pull_request_targetfor this action. It can expose secrets to PR-head code. - GitHub renders Mermaid in strict mode, so node click-through links are not supported in the PR diagram.
Fast path, no LLM calls:
scripts/run_local.sh --base-json /tmp/base.json --head-json /tmp/head.jsonFull local pipeline:
export OPENROUTER_API_KEY=sk-or-...
python -m pip install codeboarding==0.12.3
codeboarding-setup --auto-install-npm
scripts/run_local.sh --repo /path/to/repo --base <base-ref> --head <head-ref>Useful flags:
--depth N
--render-depth N
--direction LR|TD|TB|RL|BT
--changed-only
--no-edge-labels
--out DIR
--no-open
MIT. See LICENSE.