Skip to content

About

No description, website, or topics provided.

Resources

Stars

11 stars

Watchers

0 watching

Forks

Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Repository files navigation

CodeBoarding Action

One GitHub Action with two modes:

  • review (default) compares a pull request's head with its merge base, posts an inline Mermaid architecture diff, and uploads both analyses as a workflow artifact.
  • sync updates the versioned analysis state used by future incremental runs. By default it saves to a branch of its own, so code branches are never written.

The action is a thin wrapper around the CodeBoarding CLI. Analysis logic and provider defaults live in Core, not in this repository.

CodeBoarding · Website · Examples · VS Code extension · Discord

Set up

CodeBoarding runs from one workflow, .github/workflows/codeboarding.yml, with a review job and a sync job. Each job asks only for the permissions it needs. Replace main with your default branch:

name: CodeBoarding

on:
  pull_request:
    types: [opened, reopened, synchronize]
  issue_comment:
    types: [created]
  push:
    branches: [main]

permissions: {}

# One review at a time per pull request, and one sync at a time per branch. Two
# pushes in quick succession would otherwise analyze concurrently, both starting
# from the same older analysis instead of the newer one continuing from its
# predecessor, and both writing the one sticky comment, so the run for the older
# commit could win. Queue rather than cancel, so a /codeboarding command waits for
# a running review instead of killing it.
concurrency:
  group: codeboarding-${{ github.event.pull_request.number || github.event.issue.number || github.ref_name }}
  cancel-in-progress: false

jobs:
  review:
    if: >
      (github.event_name == 'pull_request' &&
       github.event.pull_request.head.repo.full_name == github.repository) ||
      (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))
    runs-on: ubuntu-latest
    timeout-minutes: 60
    permissions:
      contents: read
      actions: read        # download the analysis an earlier run published
      pull-requests: write
      issues: write
      id-token: write
    steps:
      - uses: CodeBoarding/CodeBoarding-action@v1
        with:
          llm: hosted   # or license, or a provider name -- see Authentication

  sync:
    if: github.event_name == 'push'
    runs-on: ubuntu-latest
    timeout-minutes: 60
    permissions:
      contents: write
      actions: read        # catch up from an analysis a review saved
      id-token: write
    steps:
      - uses: CodeBoarding/CodeBoarding-action@v1
        with:
          mode: sync
          llm: hosted

Both jobs use the default codeboarding_analysis_location, so review always reads the analysis where sync saves it. If you set it, set it in both jobs. Keep both jobs in this one file: a repository running CodeBoarding from more than one workflow file is an older setup, which the CodeBoarding dashboard flags and offers to fold into one.

Review pull requests

The review job's automatic runs review both draft and non-draft pull requests and update one sticky CodeBoarding review comment. Opening, reopening, or pushing a commit runs analysis; changing only the draft state does not. A trusted repository owner, member, or collaborator can comment /codeboarding to analyze the current PR head again, including on fork PRs; every command creates a new result comment.

synchronize re-runs the review on every push to the branch. Each of those runs covers only the commits pushed since the previous one, so a push costs a fraction of a first analysis — and a pushed commit is the only thing that builds the reusable analysis, since GitHub gives comment-triggered runs a read-only cache. Drop synchronize from the list if you would rather spend one analysis per pull request than one per push.

Keep the concurrency block if you keep synchronize: it is what makes a push continue from the push before it, and what stops a slower run for an older commit from overwriting the review comment for a newer one. Set cancel-in-progress: true instead to abandon a superseded run rather than queue it, which costs less when branches are pushed to rapidly, at the price of no analysis for the commits in between.

/codeboarding analyzes the current head, reusing this pull request's previous analysis when there is one. It takes no arguments.

The action checks out and analyzes the exact PR head SHA, and compares it with the PR's merge base — the commit the branch forked from, which is what GitHub's own "Files changed" tab uses. Commits pushed to the base branch after the fork point are therefore not reported as this PR's changes; the comment notes how far behind the branch is instead. It does not commit generated files to either branch.

Automatic fork runs are skipped because the pull_request event does not receive hosted OIDC credentials. A trusted /codeboarding command runs the released action code from the base repository and checks the fork's source into a separate analysis directory; it never executes an action definition from the fork with privileged credentials.

A review run uploads codeboarding-review-<run_id>-<attempt>:

File Contents
analysis.json the head analysis, at head_sha
health_report.json the head's health findings, when the engine produced any
metadata.json which commits that graph describes, and the name of the base artifact — see the field list

The graph it was compared against is published separately, named for the commit it describes, because the merge base rarely changes during a pull request and a copy per run would store the same bytes over and over. Read metadata.base_artifact and fetch that artifact by name; the graph inside it is analysis.json, and metadata.base_artifact_id records exactly which artifact this review used, since two can share a name and disagree.

Every bundle the action publishes carries a metadata.json with a kind of review, base or warmstart. Check it rather than inferring from the payload: a base bundle is otherwise indistinguishable from a head one.

The action requests 14-day retention for it; a repository or organisation policy can shorten that, so treat an artifact's own expired flag as the truth rather than any fixed window. When a pull request outlives its review artifact, /codeboarding regenerates it at the cost of one incremental over the pull request — the base graph is kept longer and does not need re-analyzing.

Reused analysis

Each review seeds the head analysis from this pull request's own previous run, so a run only covers the commits pushed since it. That analysis is published as a workflow artifact named for the pull request, and the next run fetches it by name. With nothing to fetch, the run seeds from the merge base's analysis instead, which sync publishes for every commit it processes.

Everything here is best-effort. A missing artifact, an expired one, or a token without actions: read all mean the run derives from the base instead — which is what every run did before any of this existed. Stored analyses are read only when the run that produced them worked on this repository's own code, so a fork cannot leave one behind for a later run to load. On GitHub Enterprise Server, where actions/upload-artifact@v4 is unsupported, nothing is stored or reused and every review derives from the committed baseline.

Because artifacts are readable and writable from every trigger, all paths behave the same: an automatic run, a /codeboarding command and a manual dispatch each reuse the previous analysis and publish their own. A stored analysis is discarded, and the head re-derived from the base, whenever the pinned CodeBoarding version, .codeboardingignore, the model selection, the analysis depth or the merge base changes — or when the base graph it grew from is not the one this run compares against.

Fork pull requests never carry an analysis forward. They are reviewed on request, each review starts from the base, and nothing they produce is read by a run on this repository's own code: untrusted code must not shape state that a later run loads. The action also accepts pull_request_target, which runs on the base branch ref and lets both share one chain; that trigger has its own trade-offs (a PR that adds this workflow will not run it until merged, and the fork gate becomes load-bearing), so pull_request remains the recommended default.

Pull requests that change nothing analysed

A pull request whose changed files are all outside what the engine analyses (docs, configuration, CI, tests the ignore file excludes, or a language the engine does not read) cannot have moved the architecture. The action says so by comparing the two analyses it already holds: the engine records a content hash for every file a component owns, and the review counts the analysed files whose hash differs between the base and the head, including files added or removed. At zero, no byte of analysed code changed, and the comment says (no analysed file changed) after the component count. If the count of changed components is not zero at the same time, the analysis grouped the same code differently, and the comment says that too rather than presenting it as a change. The review artifact's metadata.json carries the count as analysed_files_changed (a string, like every other field there, and unknown when either analysis has a file without a hash).

Every review comment ends with a machine-readable HTML comment, <!-- codeboarding: platform_url=… changed=… analysed_files_changed=… head=… -->, for readers that should not parse the prose or the diagram. The progress comment carries the platform link from the start, so a pull request can be opened there while the run is still going.

Authentication and providers

The llm input is required and says where analysis credentials come from. There are three answers, and the action never picks one for you:

    with:
      llm: hosted                                        # CodeBoarding's free tier
    with:
      llm: license                                       # a CodeBoarding plan
      license_key: ${{ secrets.CODEBOARDING_LICENSE }}
    with:
      llm: anthropic                                     # your own provider key
      anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}

hosted and license run through CodeBoarding's proxy and need id-token: write, which mints short-lived credentials per request and stores no LLM secret in your repository. A provider key is used directly and needs no OIDC permission.

An empty value is never a fallback. If you name a provider and its key is missing -- because the secret does not exist yet, or is misspelt — the run fails in its first seconds and says which input and which secret to fix. It does not quietly analyze on CodeBoarding's hosted tier instead. That was the old behaviour, and it meant a repository could report an Anthropic review that Anthropic never produced.

The same rule makes the combinations explicit rather than order-dependent:

Workflow says Result
nothing refused: llm is required
llm: hosted the free tier
llm: hosted + any provider key refused: pick one
llm: hosted + license_key refused: use llm: license
llm: license without license_key refused: names the secret to add
llm: anthropic + anthropic_api_key Anthropic, directly
llm: anthropic, key empty or absent refused: names the input and the secret
llm: anthropic + openai_api_key refused: a second provider's key
llm: anthropic + key + license_key Anthropic, on a CodeBoarding plan

A licence alongside your own key is deliberately allowed: it says "my CodeBoarding plan, my own tokens". Your key always wins. A direct provider call never reaches CodeBoarding, so the licence is recorded and reported but not spent, and nothing meters that combination today. The job summary says so on every run, rather than leaving you to infer it from the tier name.

Providers

Each provider has its own inputs, so which key a workflow uses is readable from the file without knowing any precedence rules.

llm Provider Inputs Needs at least one of
anthropic Anthropic anthropic_api_key anthropic_api_key
aws_bedrock AWS Bedrock aws_bedrock_api_key, aws_bedrock_region aws_bedrock_api_key
cerebras Cerebras cerebras_api_key cerebras_api_key
deepseek DeepSeek deepseek_api_key, deepseek_base_url deepseek_api_key or deepseek_base_url
glm GLM glm_api_key, glm_base_url glm_api_key or glm_base_url
google Google Gemini google_api_key google_api_key
kimi Kimi kimi_api_key, kimi_base_url kimi_api_key or kimi_base_url
litellm LiteLLM litellm_api_key, litellm_base_url litellm_base_url
ollama Ollama ollama_api_key, ollama_base_url ollama_base_url
openai OpenAI openai_api_key, openai_base_url openai_api_key or openai_base_url
openrouter OpenRouter openrouter_api_key openrouter_api_key
orcarouter OrcaRouter orcarouter_api_key orcarouter_api_key
requesty Requesty requesty_api_key, requesty_base_url requesty_api_key
vercel Vercel AI Gateway vercel_api_key, vercel_base_url vercel_api_key or vercel_base_url

Each provider has exactly one accepted spelling, and its inputs are named after it, so llm: X always pairs with X_api_key. There are no aliases: a second spelling is another thing to document and keep in step, and an unrecognised value is refused with the accepted list. ollama and litellm are selected by their endpoint rather than a key, which is why a key alone does not configure them — that mirrors how Core itself decides.

This table is generated from scripts/action/supported-providers.json, which mirrors the CodeBoarding release this action pins. tests/test_provider_table_drift.py installs that release in CI and fails if the two disagree, so a provider cannot be added to Core and silently stay unreachable here.

Reporting

Every run reports what it resolved, so the answer never has to be inferred from behaviour:

  • outputs llm_tier (hosted, license, byok, byok+license), llm_provider (empty on the hosted tiers, where the upstream is ours rather than yours), and llm_config_error (empty when configured);
  • a job-summary table naming the tier and provider;
  • on a configuration failure, an error annotation and — in review mode — a pull request comment with the fix, so the person who has to add the secret sees it where they are.
  • when the provider rejects the key or its quota runs out during the analysis, the run fails with an error annotation saying which, and in review mode the failure comment says it too. No partial analysis is published.

Model selection

All model inputs are optional and are passed directly to Core without action-side validation:

        with:
          model: google/gemini-3.7-flash
          agent_model: anthropic/claude-sonnet-4 # optional analysis-only override
          parsing_model: openai/gpt-5-mini       # optional parsing-only override

Precedence is intentionally simple:

Work Resolution
Analysis agent_model → model → active provider's Core default
Parsing parsing_model → model → active provider's Core default

Set only model when both jobs should use the same model. Set either specialized input only when that job needs a different model. Model identifiers are not secrets and can be stored in GitHub repository variables.

Keep the baseline current

Sync mode saves Core's persisted incremental-analysis state under .codeboarding/:

  • analysis.json
  • fingerprint.json
  • static_analysis.pkl
  • static_analysis.sha
  • codeboarding_version.json when emitted by Core
  • health/health_report.json

It does not render or commit architecture Markdown, and leaves every other file alone, including user-authored CodeBoarding configuration such as health/health_config.json and health/.healthignore.

Sync keeps the analysis of your repository's default branch current. codeboarding_analysis_location decides where that analysis is stored, codeboarding/baseline by default. The sync job does this, on pushes to the default branch: a sync that runs on any other branch, from any event, fails in seconds, before anything is installed.

main is only read. Each sync adds one commit to codeboarding/baseline in the same repository, an orphan branch that shares no history with main. It holds the .codeboarding/ files listed above, plus .codeboarding/source.json naming the commit they describe and the configuration that made them; the commit message carries both as CodeBoarding-Source: and CodeBoarding-Config: trailers. Pushes only ever fast-forward, and no code branch is ever written, so the push trigger needs no paths-ignore and a protected main needs no exception. Reviews read their base from the branch, and the web platform reads the latest diagram from it. The analysis branch section covers what happens if the branch is deleted, and a ruleset you should import to protect it: sync and review load a pickle from it.

The branch push itself needs nothing beyond contents: write: the first sync creates the branch with an ordinary push. If you import the ruleset that protects it, import the GitHub Actions variant: the example pushes with the default github.token, which the CodeBoarding app ruleset refuses, so the sync would fail at the push after a full analysis.

If sync pushes with an App token or a personal access token rather than the default github.token, its push to codeboarding/baseline starts your own on: push workflows, which then run on a branch with no code and fail. Exclude it from them:

on:
  push:
    branches-ignore: ['codeboarding/**']

Entries are keyed by the commit they describe, so renaming the default branch, say master to main, just continues incrementally.

The first run, or one without a compatible saved analysis to continue from, is a full analysis. Otherwise sync asks Core for an incremental update. If the generated state is unchanged, no commit is created. If the synced branch advances while analysis is running, the result is still saved for the commit it analyzed, unless the analysis branch already holds a newer commit's.

Keep the analysis on the code branch

Set codeboarding_analysis_location: in_place to commit .codeboarding/ to the synced branch itself instead, next to the code. Set it on the review job as well, so reviews read it from there, and add a paths-ignore so sync's own commit does not trigger another sync:

on:
  push:
    branches: [main]
    paths-ignore:
      - '.codeboarding/analysis.json'
      - '.codeboarding/fingerprint.json'
      - '.codeboarding/static_analysis.pkl'
      - '.codeboarding/static_analysis.sha'
      - '.codeboarding/codeboarding_version.json'
      - '.codeboarding/health/health_report.json'

# ...
      - uses: CodeBoarding/CodeBoarding-action@v1
        with:
          mode: sync
          llm: hosted
          codeboarding_analysis_location: in_place

The list names only generated files on purpose: an edit to .codeboarding/.codeboardingignore or the health configuration changes what is analyzed, and must still trigger a sync. Sync pushes to main directly, so a branch rule protecting main needs the identity sync pushes with (the CodeBoarding app, or GitHub Actions for the default token) as a bypass actor, or a github_token that may push there.

Moving an existing setup

Before this release sync committed .codeboarding/ to the synced branch by default; it now saves to codeboarding/baseline, and runs on the default branch only. A workflow that set sync_strategy: push keeps committing to the code branch, and one that set sync_strategy: pull_request moves to the analysis branch, each with a warning. A target_branch naming a branch other than the one sync runs on fails, since sync now analyzes the branch it runs on. A workflow that set none of them moves to the analysis branch; the .codeboarding/ committed on the code branch is then no longer updated. Separate review and sync workflow files still run, but belong in one file. The CodeBoarding dashboard flags all of these and opens the pull request that moves them; to make the move yourself, paste this into your coding agent:

Move this repository's CodeBoarding sync to the codeboarding/baseline analysis branch.
1. Put the review and sync jobs that run CodeBoarding/CodeBoarding-action in one
   file, .github/workflows/codeboarding.yml, as the action README's "Set up" shows,
   and delete the workflow files they came from once nothing else is left in them.
2. In those jobs, remove the sync_strategy, target_branch, force_full and
   codeboarding_analysis_location inputs if set, and any workflow_dispatch trigger
   that starts the sync job. Keep every other input. List only the default branch
   under the push trigger.
3. Delete the files sync generated from the default branch:
   .codeboarding/analysis.json, fingerprint.json, static_analysis.pkl,
   static_analysis.sha, codeboarding_version.json and health/health_report.json.
   Leave everything else under .codeboarding/.
4. Remove the paths-ignore entries for those files from the push trigger, and any
   .gitattributes lines that mark them as linguist-generated.
5. Open a pull request with these changes. After it merges, close any open
   pull request from the codeboarding/sync branch and delete that branch.

The first sync after the merge creates codeboarding/baseline, catching up from a saved analysis when there is one. To keep committing to the code branch instead, set codeboarding_analysis_location: in_place, as above.

Inputs

Input Mode Default Description
mode both review review or sync.
llm both required hosted, license, or a provider name. No default.
<provider>_api_key both empty That provider's key, e.g. anthropic_api_key. See Providers.
<provider>_base_url both empty That provider's endpoint, where it has one.
aws_bedrock_region both empty Bedrock region. Core defaults to us-east-1.
license_key both empty CodeBoarding license. Required by llm: license.
model both empty Default model for both analysis and parsing.
agent_model both empty Analysis-only override for model.
parsing_model both empty Parsing-only override for model.
depth_cap both 2 Positive integer maximum analysis depth, including full-analysis fallbacks. Changing it rebuilds incompatible state.
github_token both ${{ github.token }} Token for comments and sync delivery.
codeboarding_analysis_location both codeboarding_branch Where the synced branch's analysis is stored: codeboarding_branch (on codeboarding/baseline, for the default branch; the code is never written) or in_place (committed as .codeboarding/ on the synced branch). Sync saves it there and review reads it from there.
warmstart_retention_days review 1 Days to keep the reusable analysis. Only the next run reads it.

target_branch and sync_strategy are deprecated, and read as the codeboarding_analysis_location they stand for, with a warning: sync_strategy: push as in_place, sync_strategy: pull_request as codeboarding_branch. target_branch is accepted only when it names the branch the run is on: the push trigger picks the branch. force_full: true is ignored with a warning. Anything the new input cannot express fails, linking to moving an existing setup.

The /codeboarding command, comment heading, Mermaid direction (LR), hosted webview URL, commit message, and CodeBoarding 0.14.5 version are intentionally fixed rather than exposed as configuration.

Review mode needs no sync job or committed .codeboarding directory. If no usable merge-base analysis exists, it runs full analysis there directly, then seeds an incremental analysis of the PR head and publishes both states. Later runs prefer compatible prior PR state for incremental updates, while the review still compares the merge base with the current head.

Set depth_cap in the action's with: block (for example, depth_cap: 4). This configuration is authoritative: stored metadata.depth_cap is checked for compatibility, not inherited, and legacy metadata.depth_level is not used as a fallback. Missing or incompatible baseline depth triggers a rebuild.

metadata.depth_cap records the configured maximum; metadata.depth_level records the depth actually reached, which can be shallower. Comparing the cap avoids rejecting valid state or reducing future rebuild depth when a run stops early. The action input matches the metadata name and the engine receives only --depth-cap. There are no old-name input aliases. Historical workflows using the removed depth_level action input must switch to depth_cap.

This action pins Core 0.14.3 for the --depth-cap CLI contract. Publish that Core release before releasing the action. Earlier eShop evidence predates this final breaking CLI migration.

Outputs

Output Mode Description
llm_tier both hosted, license, byok, or byok+license.
llm_provider both Provider the run used. Empty on hosted and license: which upstream the proxy routes to is CodeBoarding's decision, not your configuration.
llm_config_error both Configuration failure code, empty when configured.
diagram_md review Path to the rendered Mermaid block on the runner.
n_changed review Number of changed components.
truncated review Whether the graph was reduced to fit GitHub limits.
review_artifact_url review URL of the uploaded head analysis.
seed_source review pr-chain when the head grew from this PR's previous analysis, base otherwise.
merge_base_sha review Merge base used as the comparison baseline.
analysis_mode sync incremental or full.
files_written sync Number of persisted analysis artifacts produced.
committed sync Whether a baseline commit was delivered.

GitHub Enterprise Server

Repository fetches, comments, and pushes use github.server_url; GitHub.com is not hardcoded for repository operations. The hosted CodeBoarding webview and LLM proxy remain CodeBoarding-operated production services.

Local test harness

Render a diff without making LLM calls:

tests/run_local.sh --base-json /tmp/base.json --head-json /tmp/head.json

Run the local analysis pipeline:

export OPENROUTER_API_KEY=sk-or-...
python -m pip install codeboarding==0.14.5
tests/run_local.sh --repo /path/to/repo --base main --head feature

License

MIT. See LICENSE.

About

No description, website, or topics provided.

Resources

Stars

11 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages