# GitHub Actions

> Cordium documentation. Canonical page: <https://octelium.com/docs/cordium/latest/examples/automation/github-actions>.

This example runs Cordium jobs from GitHub Actions without storing any Cordium or Octelium credential in GitHub. The workflows authenticate as an Octelium `WORKLOAD` *User* via the OIDC identity tokens that GitHub issues to every workflow run, which Octelium verifies before granting a short-lived *Session* (read more [here](https://octelium.com/docs/octelium/latest/management/core/identity-providers.md#oidc-assertion)). Two workflows are shown: running the test pipeline of the [CI/CD example](https://octelium.com/docs/cordium/latest/examples/automation/cicd.md) on every pull request, and dispatching the [Claude Code agent](https://octelium.com/docs/cordium/latest/examples/ai/claude-code.md) when a maintainer labels an issue.

## The Workload Identity

First, a *Cluster* administrator creates an `IdentityProvider` that trusts GitHub's OIDC tokens, and a `WORKLOAD` *User* for the repository's workflows. The *User*'s identities restrict it to the workflows of the `main` branch, of pull requests and of issue events of the `acme-corp/payments-api` repository:

```yaml
kind: IdentityProvider
metadata:
  name: github-actions
spec:
  oidcIdentityToken:
    issuerURL: https://token.actions.githubusercontent.com
    audience: https://example.com
---
kind: User
metadata:
  name: payments-api-ci
spec:
  type: WORKLOAD
  authentication:
    identities:
      - identityProvider: github-actions
        identifier: repo:acme-corp/payments-api:ref:refs/heads/main
      - identityProvider: github-actions
        identifier: repo:acme-corp/payments-api:pull_request
  authorization:
    policies: ["cordium-users"]
```

The `cordium-users` *Policy* grants access to the Cordium API (read more [here](https://octelium.com/docs/cordium/latest/management/access-control.md#authorizing-users)). Then, a *Space* admin adds `payments-api-ci` as a `USER` *Member* of the `payments` *Space* from the **Members** tab of the *Space*'s page in the web portal, which lets it create *Workspaces* from the *Space*'s *Templates* (read more [here](https://octelium.com/docs/cordium/latest/workspaces/spaces.md#members-and-roles)).

> **Note:**
>
> The `identifier` is matched against the `sub` claim of GitHub's OIDC token, whose format depends on the event that triggered the workflow (e.g. `repo:<ORG>/<REPO>:ref:refs/heads/<BRANCH>` or `repo:<ORG>/<REPO>:pull_request`). Workflows triggered by issue events use the default branch, i.e. `repo:acme-corp/payments-api:ref:refs/heads/main`.

## Running Tests on Pull Requests

The following workflow runs the `ci_run.py` script of the [CI/CD example](https://octelium.com/docs/cordium/latest/examples/automation/cicd.md#running-jobs-from-any-ci-system), committed in the repository as `.github/cordium/ci_run.py`, for every pull request. The Python SDK reads the OIDC token from the `OCTELIUM_ASSERTION` environment variable:

```yaml
name: Cordium CI
on:
  pull_request:

permissions:
  id-token: write
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.13"
      - run: pip install cordium-sdk
      - name: Get an OIDC token for Octelium
        run: |
          TOKEN="$(curl -fsSL -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
            "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://example.com" | jq -r .value)"
          echo "::add-mask::$TOKEN"
          echo "OCTELIUM_ASSERTION=$TOKEN" >> "$GITHUB_ENV"
      - name: Test in Cordium
        env:
          OCTELIUM_DOMAIN: example.com
        run: python .github/cordium/ci_run.py "${{ github.event.pull_request.head.sha }}"
```

The job's log shows the output of every step that runs in the Cordium *Workspace*, and the job fails if any of them fails. Note that GitHub does not issue OIDC tokens to the workflows of pull requests from forks.

## Dispatching an AI Agent from Issues

The following workflow starts a run of the Claude Code agent whenever a maintainer adds the `agent` label to an issue. It uses the `cordium` CLI, which fetches GitHub's OIDC token by itself when `OCTELIUM_AUTH_ASSERTION` is set to `github-actions` (read more [here](https://octelium.com/docs/cordium/latest/use/cli.md#authentication)):

```yaml
name: Cordium agent
on:
  issues:
    types: [labeled]

permissions:
  id-token: write
  issues: write

jobs:
  dispatch:
    if: github.event.label.name == 'agent'
    runs-on: ubuntu-latest
    env:
      OCTELIUM_DOMAIN: example.com
      OCTELIUM_AUTH_ASSERTION: github-actions:audience=https://example.com
      GH_TOKEN: ${{ github.token }}
      ISSUE_NUMBER: ${{ github.event.issue.number }}
      ISSUE_TITLE: ${{ github.event.issue.title }}
      ISSUE_BODY: ${{ github.event.issue.body }}
    steps:
      - name: Install the Cordium CLI
        run: curl -fsSL https://octelium.com/install-cordium.sh | bash
      - name: Start the agent
        run: |
          TASK="$(printf 'Fix #%s: %s\n\n%s' "$ISSUE_NUMBER" "$ISSUE_TITLE" "$ISSUE_BODY")"
          cordium create ws --template claude-agent.payments.cordium --ephemeral --start \
            --var TASK="$TASK"
          gh issue comment "$ISSUE_NUMBER" --repo "$GITHUB_REPOSITORY" \
            --body "A Cordium agent is working on this issue and will open a draft pull request."
```

The issue's content is passed to the workflow's script via environment variables rather than being expanded into the script itself, which keeps it from being interpreted by the shell. The agent's *Workspace* then runs as described in the [Claude Code example](https://octelium.com/docs/cordium/latest/examples/ai/claude-code.md), with its own secretless access to the Anthropic API and GitHub.

> **Note:**
>
> The issue's content is untrusted input for the agent: anyone who can open an issue can attempt a prompt injection. This is why the workflow only runs when a maintainer adds the label, and why the agent's *Workspace* holds no credentials and can only reach one repository through Octelium. Protect your main branch with branch protection rules so that the agent's changes always go through a reviewed pull request, and treat them like those of any external contributor.
