> For the complete documentation index, see [llms.txt](https://docs.codna.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.codna.ai/guides/github-action.md).

# GitHub Action

thyn-ai/codna-action\@v1 runs codna fix, review or secure from your own CI. It installs codna from PyPI and runs the same commands you run on your machine.

The `codna` GitHub Action runs Codna from your own CI. In `fix` mode it turns a failing run into a fix pull request. In `review` mode it reviews a pull request and posts the findings and the `codna review` check. In `secure` mode it proves which scanner findings are reachable.

The action is a thin wrapper over the CLI: it installs `codna` from PyPI, then runs `codna fix --open-pr`, `codna review --post`, or `codna secure`. It adds CI plumbing and nothing else. What it posts reads the same as the [GitHub App](/guides/github-app.md) and as the CLI on your machine.

{% hint style="info" %}
`thyn-ai/codna-action@v1` installs the published `codna` wheel before it runs. The runtime ships inside the wheel, so **no Node or Bun setup is needed** on the runner. Pin the package with the `package-spec` input (see [Pinning](#pinning)).
{% endhint %}

## What it does

The composite action runs exactly two steps:

1. **Install Codna** with `pipx install --pip-args=--only-binary=codna --force <package-spec>`, falling back to `python3 -m pip install --user --upgrade --only-binary=codna <package-spec>`. The default `package-spec` is `codna`, the latest published release.
2. **Run `codna`** against the repository and commit of the workflow run.

{% code title="fix mode (effective command)" %}

```bash
codna fix "https://github.com/$OWNER/$REPO.git" \
  --ref "$GITHUB_SHA" \
  --open-pr \
  --json \
  --model "repository.verified_agentic_v1" \
  [--issue "$ISSUE"] \
  [--from-junit "$JUNIT_PATH"] \
  [--base-branch "$BRANCH"]
```

{% endcode %}

In review mode on `pull_request` events, the action fetches the PR base branch and reviews `origin/<base>...HEAD`. Outside `pull_request` events, set the `diff` input explicitly; the action fails rather than reviewing `HEAD` against itself.

{% code title="review mode (effective command)" %}

```bash
codna review . \
  --pr "$PR_NUMBER" \
  --json \
  --model "repository.verified_agentic_v1" \
  --diff "origin/$BASE...HEAD" \
  [--post] \
  [--min-confidence "$N"] \
  [--blocking] \
  [--effort "low|medium|high"]
```

{% endcode %}

In secure mode, the same step runs the read-only SARIF reachability classification:

{% code title="secure mode (effective command)" %}

```bash
codna secure "https://github.com/$OWNER/$REPO.git" \
  --ref "$GITHUB_SHA" \
  --from-sarif "$SARIF_PATH" \
  --engine "remote|local" \
  [--verification "codna-security.yaml"]
```

{% endcode %}

The step keeps runtime logs on stderr and fix-mode stdout as the CLI's JSON result. It reads the top-level `pull_request_url` field and, when that field is empty or the output is not JSON, scans the output for a `https://github.com/.../pull/<n>` URL. The value is published as the `pull-request-url` output and written to the job summary. Review and secure modes always publish an empty `pull-request-url`.

## Inputs

| Input                 | Required | Default                          | Description                                                                                                                                                                                                                        |
| --------------------- | -------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mode`                | no       | `fix`                            | `fix` opens a fix PR; `review` posts findings and the `codna review` check; `secure` runs read-only SARIF reachability classification.                                                                                             |
| `package-spec`        | no       | `codna`                          | The PyPI spec to install. Pin for deterministic CI, e.g. `codna==0.2.76`.                                                                                                                                                          |
| `model`               | no       | `repository.verified_agentic_v1` | Planner/review model. Use `<provider>/<model-id>` and set that provider's key in the job environment. Without a provider prefix the default provider is Anthropic, so `ANTHROPIC_API_KEY` must be set.                             |
| `issue`               | no       | —                                | Fix mode: what is broken. Optional if `from-junit` is set.                                                                                                                                                                         |
| `from-junit`          | no       | —                                | Fix mode: path to a JUnit/pytest XML report; Codna derives the failing tests from it.                                                                                                                                              |
| `base-branch`         | no       | repo default branch              | Fix mode: base branch for the pull request.                                                                                                                                                                                        |
| `pr`                  | no       | triggering PR                    | Review mode: the PR to review/post to (number, `owner/repo#123`, or a PR URL).                                                                                                                                                     |
| `diff`                | no       | PR base...head                   | Review mode: diff range to review, for example `origin/main...HEAD`.                                                                                                                                                               |
| `post`                | no       | `true`                           | Review mode: post the findings + check. `false` computes findings only.                                                                                                                                                            |
| `min-confidence`      | no       | `0.75`                           | Review mode: only report findings at or above this confidence.                                                                                                                                                                     |
| `blocking`            | no       | `false`                          | Review mode: `true` fails the `codna review` check on a blocking-severity finding.                                                                                                                                                 |
| `effort`              | no       | `medium`                         | Review mode: `low` \| `medium` \| `high`.                                                                                                                                                                                          |
| `from-sarif`          | no       | —                                | Secure mode: path to the SARIF report. Required when `mode: secure`.                                                                                                                                                               |
| `reachability-engine` | no       | `remote`                         | Secure mode: `remote` sends classification to the Codna engine over HTTP; `local` uses the bounded in-process classifier, the CLI's default. See [Security Autofix](/guides/security-autofix.md#choosing-the-reachability-engine). |
| `verification`        | no       | —                                | Secure mode: optional `codna-security.yaml` manifest. The action stays read-only.                                                                                                                                                  |
| `api-key`             | no       | —                                | Your Codna key, exported as `CODNA_API_KEY`. Only needed when the run is pointed at a remote engine via `CODNA_ENGINE_URL`. The packaged local runtime does not use it.                                                            |
| `github-token`        | no       | `${{ github.token }}`            | The token Codna uses to push a branch and open a PR (fix) or post a review (review). Defaults to the workflow token.                                                                                                               |

Provide at least one of `issue` or `from-junit` in fix mode so the agent knows what to fix.

## Outputs

| Output             | Description                                                                                                   |
| ------------------ | ------------------------------------------------------------------------------------------------------------- |
| `pull-request-url` | URL of the pull request Codna opened. Empty if no PR was opened, and always empty in review and secure modes. |

## Permissions and secrets

| Mode     | Job permissions                                                         |
| -------- | ----------------------------------------------------------------------- |
| `fix`    | `contents: write`, `pull-requests: write`                               |
| `review` | `contents: read`, `pull-requests: write`, `checks: write`               |
| `secure` | None beyond the default token. The mode is read-only and opens nothing. |

One secret is required: a **model provider key** in the job environment, matching the `model` input (`OPENAI_API_KEY` for `openai/…`, `ANTHROPIC_API_KEY` for `anthropic/…` or for a model with no provider prefix, and so on). `CODNA_API_KEY` is not required.

The GitHub token is supplied automatically by `${{ github.token }}`. Override `github-token` only if you need a different identity: to open PRs as a bot account, to open PRs across repositories, or when an organization policy blocks GitHub Actions from creating pull requests.

{% hint style="warning" %}
The action **opens** a pull request; it never merges. Branch protection and required reviews still apply. A fix lands only after a human approves it.
{% endhint %}

## What each mode produces

| Mode     | Result                                                                                                                                                                                                                                                                                                                                                                        |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fix`    | A pull request on a branch named `codna/<plan-id>`. Its body states the issue, the root cause, the symbols touched and a confidence score, and ends with `_Review before merging._` The run returns once the PR exists; your CI then runs on the branch.                                                                                                                      |
| `review` | One review on the PR and one `codna review` check. Findings carry severity, category and confidence; a clean diff at medium and high posts an **Approve**; a red required check is named next to the verdict; dependency claims are checked against npm and PyPI before posting. Exactly what the [GitHub App](/guides/github-app.md#what-you-see-on-the-pull-request) posts. |
| `secure` | The per-finding reachability verdicts on stdout and in the job summary. No pull request.                                                                                                                                                                                                                                                                                      |

## Pinning

Pin both the package and the wrapper for a reproducible workflow:

```yaml
- uses: thyn-ai/codna-action@3fbffb7b4f9e5d93d4b8a59eee13520698fbbe3f # v1
  with:
    package-spec: codna==0.2.76
```

`package-spec` fixes the `codna` version; `--only-binary=codna` means the wheel published to PyPI is what runs. `@v1` is a floating tag that maintainers move to each vetted change of the wrapper; the full commit SHA above is `v1` today. A Dependabot `github-actions` entry keeps the pin current.

## Example workflow: fix a failing CI run

Drop this into `.github/workflows/codna-autofix.yml`. It waits for your existing CI workflow to finish, and when that run fails, asks Codna to open a fix PR.

The job checks out the **default branch**, never `github.event.workflow_run.head_sha`: a `workflow_run` job runs with the repository's secrets and a write token, so checking out and running the failing commit (for a pull request, the untrusted PR head) would let that code read secrets and push as the workflow. The action does not need the failing commit in the workspace; it clones the repository itself.

{% hint style="info" %}
In a `workflow_run` job, `github.sha` is the default branch head, so this workflow fixes failures on the default branch. To fix a red check suite on a pull request branch, install the [GitHub App](/guides/github-app.md#from-red-ci-on-a-pull-request): it triages the failure first and opens the fix on the PR branch.
{% endhint %}

{% tabs %}
{% tab title="Minimal workflow" icon="bolt" %}
{% code title="codna-autofix.yml" lineNumbers="true" %}

```yaml
name: codna auto-fix

on:
  workflow_run:
    workflows: ["CI"]          # the name of your existing CI workflow
    types: [completed]

permissions:
  contents: write
  pull-requests: write

jobs:
  codna-fix:
    if: ${{ github.event.workflow_run.conclusion == 'failure' }}
    runs-on: ubuntu-latest
    steps:
      # Default-branch checkout only: never `ref: ${{ github.event.workflow_run.head_sha }}`
      # in a workflow_run job (it has secrets and a write token).
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false

      - uses: thyn-ai/codna-action@3fbffb7b4f9e5d93d4b8a59eee13520698fbbe3f # v1
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        with:
          issue: "CI failed on ${{ github.event.workflow_run.head_branch }}"
          model: openai/gpt-5
```

{% endcode %}
{% endtab %}

{% tab title="Full workflow" icon="list-check" %}
{% code title="codna-autofix.yml" lineNumbers="true" %}

```yaml
# When your CI workflow fails, codna opens a PR with the fix.
# Copy this into .github/workflows/ in your repo and set a provider key secret
# (OPENAI_API_KEY here; ANTHROPIC_API_KEY, GEMINI_API_KEY, … for other providers).
name: codna auto-fix

on:
  workflow_run:
    workflows: ["CI"]          # the name of your existing CI workflow
    types: [completed]

# codna opens a PR, so the job needs write scope:
permissions:
  contents: write
  pull-requests: write

jobs:
  codna-fix:
    if: ${{ github.event.workflow_run.conclusion == 'failure' }}
    runs-on: ubuntu-latest
    steps:
      # A workflow_run job runs with this repository's secrets and a write token, so it must only
      # execute code from the default branch -- which is what a plain checkout gives you here.
      # Never check out `github.event.workflow_run.head_sha`: for a pull request that is the
      # untrusted PR head, and running it in this job would let it read secrets and push as you.
      # codna-action does not need the failing commit in the workspace; it clones the repository
      # itself. Actions are pinned to full commit SHAs; a Dependabot `github-actions` entry keeps
      # the pins current.
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false

      - uses: thyn-ai/codna-action@3fbffb7b4f9e5d93d4b8a59eee13520698fbbe3f # v1
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        with:
          package-spec: codna==0.2.76
          issue: "CI '${{ github.event.workflow_run.name }}' failed on ${{ github.event.workflow_run.head_branch }}"
          model: openai/gpt-5
          # or, if you upload a JUnit report as an artifact / have it on disk:
          # from-junit: reports/junit.xml
```

{% endcode %}
{% endtab %}
{% endtabs %}

### End-to-end: a failing test becomes a fix PR

Follow this start to finish in a repository that already has a CI workflow named `CI`.

{% stepper %}
{% step %}

#### Add the secret

In the repo, go to **Settings → Secrets and variables → Actions** and add your model provider key, for example `OPENAI_API_KEY`.
{% endstep %}

{% step %}

#### Add the workflow

Add a workflow from the **Example workflow** tabs above as `.github/workflows/codna-autofix.yml` and commit it to the default branch.
{% endstep %}

{% step %}

#### Break something

Push a commit to the default branch that makes a test fail, for example `tests/test_checkout.py::test_total`.
{% endstep %}

{% step %}

#### CI runs and fails

The `CI` workflow completes with conclusion `failure`.
{% endstep %}

{% step %}

#### codna auto-fix triggers

The `workflow_run` event fires; the `if` guard passes because the conclusion is `failure`; the job checks out the default branch and runs the action, which clones the repository itself. In the **codna fix / review / secure** step you see the CLI's JSON result, and the job summary shows:

```
### codna
Opened a PR: https://github.com/acme/shop/pull/482
```

{% endstep %}

{% step %}

#### Review the PR

Open the pull request, read the diff and the root cause in the PR body, and merge if it is correct. The action never merges for you.
{% endstep %}
{% endstepper %}

The same URL is the `pull-request-url` step output.

### Driving from a JUnit report

If your CI emits a JUnit/pytest XML report, point the action at it instead of writing the issue text by hand; Codna derives the failing tests directly:

{% code title="codna-autofix.yml (JUnit step)" %}

```yaml
      - uses: thyn-ai/codna-action@3fbffb7b4f9e5d93d4b8a59eee13520698fbbe3f # v1
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        with:
          from-junit: reports/junit.xml
          model: openai/gpt-5
```

{% endcode %}

The report must be present on disk in the job at the given path. If your CI uploads it as an artifact, download it (with `actions/download-artifact`) before the Codna step so the file exists.

### Consuming the PR URL

The opened PR URL is available to later steps via the `pull-request-url` output:

{% code title="codna-autofix.yml (consume output)" %}

```yaml
      - uses: thyn-ai/codna-action@3fbffb7b4f9e5d93d4b8a59eee13520698fbbe3f # v1
        id: codna
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        with:
          issue: "CI failed on ${{ github.ref_name }}"
          model: openai/gpt-5

      - name: Show the PR
        if: ${{ steps.codna.outputs.pull-request-url != '' }}
        run: echo "codna opened ${{ steps.codna.outputs.pull-request-url }}"
```

{% endcode %}

## Example workflow: review every pull request

See [codna review § From the GitHub Action](/guides/review.md#from-the-github-action) for the complete `mode: review` workflow. It needs `contents: read`, `pull-requests: write`, `checks: write`, a checkout with `fetch-depth: 0`, and a provider key.

## Security-proof flow (two-job privilege separation)

For security remediation, split the work across two jobs in two privilege domains. The analysis job runs untrusted code (the scanner, build, tests, and the generated patch) with **no write token** and uploads a signed evidence bundle. A separate writer job holds the write token, executes none of the project's code, re-verifies the attestation, and opens a **draft** PR with `codna secure-open-pr`.

```mermaid
flowchart LR
    subgraph analyze["Analyze job · contents: read · NO write token"]
        direction TB
        scan[Scanner / build / tests] --> gen[Generate + verify patch] --> sign[Sign evidence bundle]
    end
    sign -->|upload signed artifact| writer
    subgraph writer["Writer job · contents + pull-requests: write · runs no repo code"]
        direction TB
        verify[Re-verify signature,<br/>patch digest, base commit] --> draft[Open draft PR]
    end
    draft --> human([Human review])
```

The analysis job uses the CLI directly, not the action: writing evidence needs `--fix --engine remote --verification … --evidence-dir`. `--engine remote` sends classification to the Codna engine over HTTP: the local runtime Codna starts in the job, or the engine named by `CODNA_ENGINE_URL` together with `CODNA_API_KEY`.

{% code title="codna-secure.yml" lineNumbers="true" %}

```yaml
name: codna secure

on:
  workflow_run:
    workflows: ["CI"]
    types: [completed]
  workflow_dispatch:

permissions: {}            # default: nothing — each job opts in to the minimum it needs

jobs:
  analyze:
    runs-on: ubuntu-latest
    permissions:
      contents: read       # read-only checkout; NO write credential reaches untrusted code
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false

      # Run your existing scanner -> SARIF (codna is scanner-agnostic; CodeQL/Semgrep/Snyk/Trivy).
      - name: Scan (Semgrep -> SARIF)
        run: |
          pipx install semgrep || python3 -m pip install --user semgrep
          semgrep --config p/ci --sarif --output results.sarif || true

      - name: Install codna
        run: pipx install --pip-args=--only-binary=codna codna==0.2.76

      # Prove reachability, remediate in the sandbox, and write the signed evidence bundle.
      # No GITHUB_TOKEN here.
      - name: codna secure (analyze)
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          CODNA_ATTESTATION_KEY: ${{ secrets.CODNA_ATTESTATION_KEY }}
          # Optional: point --engine remote at a Codna engine instead of the local runtime.
          # CODNA_ENGINE_URL: ${{ secrets.CODNA_ENGINE_URL }}
          # CODNA_API_KEY: ${{ secrets.CODNA_API_KEY }}
        run: |
          codna secure . \
            --from-sarif results.sarif \
            --ref "${{ github.sha }}" \
            --engine remote --fix \
            --verification codna-security.yaml \
            --evidence-dir ./evidence

      - name: Upload evidence (attestation + patch)
        uses: actions/upload-artifact@v4
        with:
          name: codna-evidence
          path: evidence/
          if-no-files-found: ignore

  open-pr:
    needs: analyze                    # writer runs only after a successful analysis
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write            # the ONLY job with a write-capable token
    steps:
      - name: Download evidence
        uses: actions/download-artifact@v4
        with:
          name: codna-evidence
          path: evidence

      - name: Install codna
        run: pipx install --pip-args=--only-binary=codna codna==0.2.76

      # The writer re-verifies the signed attestation — signature, patch digest, base commit
      # unchanged — then opens a DRAFT PR with this job's token. It executes NONE of the
      # project's code.
      - name: codna secure-open-pr (writer)
        env:
          GITHUB_TOKEN: ${{ github.token }}
          CODNA_ATTESTATION_KEY: ${{ secrets.CODNA_ATTESTATION_KEY }}
        run: |
          codna secure-open-pr --evidence evidence/ \
            --repo-slug "${{ github.repository }}" \
            --base-branch "${{ github.event.repository.default_branch }}"
```

{% endcode %}

Key properties of this layout:

* `permissions: {}` at the top means the default for both jobs is no token at all; each job opts into the minimum it needs.
* The `analyze` job has `contents: read` only. A write token never enters the environment that runs untrusted code.
* The `open-pr` job is the only place a write-capable token exists, it `needs: analyze`, and it runs no project code, only `codna secure-open-pr` against the verified evidence.
* `CODNA_ATTESTATION_KEY` must be the same secret in both jobs. The writer refuses to open anything it cannot verify.

See [Security Autofix](/guides/security-autofix.md) for the evidence format and the `codna secure` command.

## Troubleshooting

<details>

<summary>No pull request was opened (<code>pull-request-url</code> is empty)</summary>

The step ran but produced no PR URL. Common causes:

* **No `issue` or `from-junit` supplied.** The agent has nothing to fix. Set at least one of these inputs.
* **`from-junit` path does not exist** in the job. Download the report artifact before the Codna step, or use `issue` instead.
* **The agent declined to patch** (for example, the risk simulation rejected every candidate). The full CLI output is written to the job summary inside a fenced block; read it there for the reason.
* **Review or secure mode.** These modes never open a pull request, so the output is empty by design.

</details>

<details>

<summary>Permission errors when opening the PR</summary>

If the CLI output contains a `403` or a message like `Resource not accessible by integration`, the workflow token lacks write scope. Add the permissions block to the job (or the workflow):

```yaml
permissions:
  contents: write
  pull-requests: write
```

If you are running from a fork or a `workflow_run` triggered by a fork, the default token may be read-only regardless of this block; supply a `github-token` with the required scopes instead.

</details>

<details>

<summary>The model call fails</summary>

* Confirm the provider key that matches the `model` input is set in the job environment (`OPENAI_API_KEY` for `openai/…`, `ANTHROPIC_API_KEY` for `anthropic/…` or for a model with no provider prefix).
* Run the same `codna fix` command locally to see the full error.

</details>

<details>

<summary>Codna failed to install</summary>

The action installs `codna` from PyPI as a wheel. On a self-hosted runner, ensure Python 3.12 or newer and `pip` or `pipx` are installed, and that the runner is Linux or macOS, the platforms Codna publishes wheels for.

</details>

<details>

<summary>Review mode fails with "needs a diff range"</summary>

Outside `pull_request` events the action does not know the base branch. Set `diff`, for example `diff: origin/main...HEAD`, and check out with `fetch-depth: 0` so the range exists.

</details>

<details>

<summary>The workflow never triggers</summary>

If a CI failure does not start the auto-fix job:

* The `workflows: ["CI"]` value must match your CI workflow's `name:` exactly (case-sensitive).
* The `if: ${{ github.event.workflow_run.conclusion == 'failure' }}` guard means the job runs only on failure; a passing CI run does nothing.
* `workflow_run` workflows must live on the default branch to be picked up by GitHub.

</details>

## Next steps

* [CLI Reference](/reference/cli.md) — reproduce the same `fix`, `review`, and `secure` commands locally.
* [codna review](/guides/review.md) — what review mode posts, and the `review:` block.
* [Security Autofix](/guides/security-autofix.md) — secure mode, SARIF, and the two-job writer.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.codna.ai/guides/github-action.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
