> 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

The `codna` GitHub Action turns a failing CI run into a fix pull request. Use it when you want a failing build or test job to automatically produce a reviewable fix PR — no developer has to notice the failure, reproduce it, and write the patch by hand.

The action is a thin adapter over `codna fix --open-pr`: it installs the CLI, runs the agent loop (triage, fix, risk simulation) on the failing commit, and opens a PR. It adds only CI plumbing and the write-scoped token.

{% hint style="info" %}
The public action `thyn-ai/codna-action@v1` installs the published `codna` package from PyPI before it runs, then uses the same packaged local runtime as `codna fix`. To pin a version, set the `CODNA_ACTION_PACKAGE_SPEC` environment variable on the step (see [Pinning a version](#pinning-a-version)).
{% endhint %}

## What it does

The composite action runs exactly two steps:

1. **Install Codna** — installs the public PyPI package with `pipx install --pip-args=--only-binary=codna`, falling back to `python3 -m pip install --user --upgrade --only-binary=codna`. By default it installs the latest published `codna` (when run from a checkout, it pins to the version in `cli/codna/__init__.py`). Since `codna` 0.1.35 the wheel bundles a self-contained agent-core sidecar (its JavaScript runtime is compiled in), so **no Node or Bun setup is needed** on the runner.
2. **codna fix, review, or secure** — invokes the CLI against the current repository and commit:

{% code title="action step (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 "<path>"] \
  [--base-branch "<branch>"]
```

{% endcode %}

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

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

```bash
codna review . \
  --pr "<pull_request.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 invokes read-only Tier-1 SARIF reachability classification:

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

```bash
codna secure "https://github.com/<owner>/<repo>.git" \
  --ref "<github.sha>" \
  --from-sarif "<results.sarif>" \
  --engine "local|remote" \
  [--verification "<codna-security.yaml>"]
```

{% endcode %}

The step keeps runtime logs on stderr and keeps fix-mode stdout as the CLI's JSON result. It reads the top-level `pull_request_url` field first, then falls back to scanning for a `https://github.com/.../pull/<n>` URL only for non-JSON output. The resulting value is published as the `pull-request-url` output and written to the GitHub job summary. Secure mode is read-only in this single-step Action and intentionally produces an empty `pull-request-url`.

The action runs as a `composite` action. It exports the `api-key` input as `CODNA_API_KEY` (empty when unset) and `github-token` as `GITHUB_TOKEN`; repository intelligence and verified planning use the packaged local Codna runtime.

## Pinning a version

The action has no `package-spec` input. To pin the `codna` version for deterministic CI, set the `CODNA_ACTION_PACKAGE_SPEC` environment variable on the step (not `with:`):

```yaml
- uses: thyn-ai/codna-action@v1
  env:
    CODNA_ACTION_PACKAGE_SPEC: codna==0.1.43
  with:
    api-key: ${{ secrets.CODNA_API_KEY }}
```

If it's unset, the action installs the latest published `codna` — unless it runs from a checkout, where it pins to the version in `cli/codna/__init__.py`.

## Privilege separation

The action keeps analysis and writing on separate authorities, so the credential that can change your repository is never the credential that runs the fix.

| Concern                 | Credential                                                                                | Scope                                                     |
| ----------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| Analysis / fix (worker) | packaged local Codna runtime plus optional `api-key` → `CODNA_API_KEY` account credential | Never holds the GitHub write token.                       |
| Open the PR (writer)    | `github-token` → `GITHUB_TOKEN`                                                           | Used solely to push the branch and open the pull request. |

The fix worker produces a patch and a risk verdict without holding the GitHub write capability. The PR writer receives `github-token` as `GITHUB_TOKEN`, scoped to `contents: write` plus `pull-requests: write`, and only opens a reviewable pull request. A compromised analysis path cannot, on its own, push to a protected branch or merge anything: the writer holds those rights, and it only opens a PR for human review.

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

## Inputs

| Input                 | Required | Default                          | Description                                                                                                                                                         |
| --------------------- | -------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `api-key`             | **yes**  | —                                | Codna license / metering key (maps to `CODNA_API_KEY`).                                                                                                             |
| `mode`                | no       | `fix`                            | `fix` opens a fix PR; `review` posts read-only inline PR findings + a non-blocking "Codna Review" check; `secure` runs read-only SARIF reachability classification. |
| `issue`               | no       | —                                | What is broken (for example, the failing test name plus error). Optional if `from-junit` is set.                                                                    |
| `from-junit`          | no       | —                                | Path to a JUnit/pytest XML report; Codna derives the failing tests from it.                                                                                         |
| `from-sarif`          | no       | —                                | Path to a SARIF report. Required when `mode: secure`.                                                                                                               |
| `reachability-engine` | no       | `remote`                         | Secure-mode engine: `remote` for the hosted/engine-backed proof path, or `local` for bounded self-hosted classification.                                            |
| `verification`        | no       | —                                | Optional `codna-security.yaml` manifest for secure-mode policy sharpening. This Action still stays read-only.                                                       |
| `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. Set `false` to compute findings only (dry run).                                                                             |
| `min-confidence`      | no       | `0.75`                           | Review mode: only report findings at or above this confidence.                                                                                                      |
| `blocking`            | no       | `false`                          | Review mode: `true` fails the check on a blocking-severity finding.                                                                                                 |
| `effort`              | no       | `medium`                         | Review mode: review depth — `low` \| `medium` \| `high` (higher is more thorough).                                                                                  |
| `base-branch`         | no       | repo default branch              | Base branch for the pull request.                                                                                                                                   |
| `model`               | no       | `repository.verified_agentic_v1` | Planner model.                                                                                                                                                      |
| `github-token`        | no       | `${{ github.token }}`            | Token with `contents: write` plus `pull-requests: write`. Defaults to the workflow token.                                                                           |

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

{% hint style="info" %}
**Review mode** needs only `contents: read`, `pull-requests: write`, and `checks: write` — no `contents: write` token, because it never pushes. Trigger it on `pull_request` events. See [Codna Review](/guides/review.md) for a complete `mode: review` workflow.
{% endhint %}

## Outputs

| Output             | Description                                                      |
| ------------------ | ---------------------------------------------------------------- |
| `pull-request-url` | URL of the pull request Codna opened. Empty if no PR was opened. |

In fix mode, the output is read from the CLI JSON field `pull_request_url` when available, with a URL regex fallback for non-JSON output. In secure mode, or if the agent produced no PR, the value is an empty string.

## Permissions and secrets

The job must grant the workflow token write access so the action can push a branch and open the PR:

{% code title="job permissions" %}

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

{% endcode %}

One secret is required:

| Secret          | Used for                                                                         |
| --------------- | -------------------------------------------------------------------------------- |
| `CODNA_API_KEY` | Authenticates the analysis worker to the engine (passed as the `api-key` input). |

The GitHub token is supplied automatically by `${{ github.token }}`. Override `github-token` only if you need a different identity (for example, to open PRs as a bot account, to open PRs across repositories where the default workflow token has no write access, or when an enterprise/org policy blocks GitHub Actions from creating pull requests even with `contents: write` and `pull-requests: write` permissions).

## Example workflow

Drop one of these 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 against the failing commit. The full variant is the canonical setup shipped in `examples/codna-autofix.yml`.

{% tabs %}
{% tab title="Minimal workflow" icon="bolt" %}
The smallest setup that works: trigger on a failed `CI` run, grant write scope, run the action with a single `issue` input.

{% 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:
      - uses: actions/checkout@v7
        with:
          ref: ${{ github.event.workflow_run.head_sha }}

      - uses: thyn-ai/codna-action@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" %}
The canonical setup shipped in `examples/codna-autofix.yml`, with the JUnit and self-hosted-engine options called out inline.

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

```yaml
# Example: 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.
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:
      - uses: actions/checkout@v7
        with:
          ref: ${{ github.event.workflow_run.head_sha }}

      - uses: thyn-ai/codna-action@v1
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        with:
          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 `CODNA_API_KEY` with your Codna 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 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 failing commit and runs the action. In the **codna fix → open PR** step you will see output like this:

{% code title="codna fix → open PR (job log)" %}

```
codna 0.x
triaging https://github.com/acme/shop.git @ 9f3c2a1 ...
  localized: src/checkout/totals.py:42 (confidence 0.91)
planning fix (model: repository.verified_agentic_v1) ...
applying patch (1 file, +3 -1) ...
simulating patch risk ...
  risk: low — no new failing tests, 1 test now passing
pushing branch codna/fix-test-total-9f3c2a1 ...
opened pull request: https://github.com/acme/shop/pull/482
```

{% endcode %}
{% endstep %}

{% step %}

#### Review the PR

Open `https://github.com/acme/shop/pull/482`, read the diff and the risk summary in the PR body, and merge if it is correct. The action never merges for you.
{% endstep %}
{% endstepper %}

The same URL appears in the job summary (under a **codna** heading) and as 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@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@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 %}

{% hint style="success" %}
Minimal setup is one job plus a model provider key for local-runtime fixes. Add the workflow above, set `OPENAI_API_KEY` or another supported provider key, and a failing CI run will produce a fix PR for review.
{% endhint %}

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

For security remediation, Codna ships a stronger pattern in `examples/codna-secure.yml`: two jobs in two privilege domains, so the security boundary is structural rather than a setting. The analysis worker runs untrusted code (the scanner, build, tests, and the generated patch) with **no write token**; it uploads a signed evidence attestation. A separate writer job holds the write token, executes none of the project's code, re-verifies the attestation, and opens a **draft** 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])
```

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

```yaml
# codna security-proof autofix — privilege-separated, evidence-backed.
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@v7

      # Run your existing scanner -> SARIF (codna is scanner-agnostic; swap in CodeQL/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 codna || python3 -m pip install --user codna

      # Prove reachability + (with a resolved manifest) remediate in the sandbox and emit a
      # signed attestation. No GITHUB_TOKEN here.
      - name: codna secure (analyze)
        env:
          CODNA_API_KEY: ${{ secrets.CODNA_API_KEY }}
        run: |
          codna secure "." \
            --from-sarif results.sarif \
            --ref "${{ github.sha }}" \
            --verification codna-security.yaml

      - name: Upload evidence (attestation + patch)
        uses: actions/upload-artifact@v4
        with:
          name: codna-evidence
          path: .codna/evidence/      # signed attestation + patch produced by the worker
          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 codna || python3 -m pip install --user codna

      # The writer re-verifies the signed attestation — signature, patch digest, single-use
      # nonce, and that the base commit has not moved — then opens a DRAFT PR with this job's
      # token. It executes NONE of the project's code.
      - name: codna open-pr (writer)
        env:
          GITHUB_TOKEN: ${{ github.token }}
          CODNA_API_KEY: ${{ secrets.CODNA_API_KEY }}
        run: |
          echo "writer domain: verify evidence/ attestation and open the draft PR"
          # codna secure-open-pr --evidence evidence/ --base-branch "${{ github.event.repository.default_branch }}"
```

{% endcode %}

{% hint style="warning" %}
The writer step in this example is the thin CLI adapter over `codna`'s `GitHubWriter`. The writer's verification logic (signature, patch digest, single-use nonce, base-commit-unchanged) is implemented and tested today; the `codna secure-open-pr` adapter activates once the remediation agent the worker invokes is configured to emit the evidence artifact. The `analyze`/`open-pr` privilege split, the read-only worker token, and the artifact handoff work as written now.
{% endhint %}

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` against the verified evidence artifact.

See the **Security** page for the evidence/attestation format and the `codna secure` command details.

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

</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>Engine unreachable / authentication failures</summary>

Errors that mention a connection timeout from the local sidecar or runtime point at the packaged local runtime:

* Confirm the `CODNA_API_KEY` secret is set and passed as the `api-key` input.
* Confirm the selected provider key, such as `OPENAI_API_KEY`, is set in the job environment.
* Run the same `codna fix` command locally or inspect the Action logs for `codna doctor` output.

</details>

<details>

<summary>Codna failed to install</summary>

The public action installs `codna` from PyPI. On a minimal self-hosted runner, ensure Python 3.10+ and `pip` or `pipx` are installed before the job runs the action. To pin a version, see [Pinning a version](#pinning-a-version).

</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 is expected to do 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` modes locally.
* [Configuration](/reference/configuration.md) — configure GitHub tokens, provider keys, and hosted App settings.
* [Security Autofix](/guides/security-autofix.md) — use secure mode with SARIF and the verified PR gate.


---

# 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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
