GitHub Actions runners

GitHub Actions execution uses the same runner claim, hosting and storage contracts as other placements. The controller verifies OIDC exchange through the shared capability policy. Native runner startup and workflow execution require separate integration evidence.

A team binds a repository through its GitHub Actions registration. The workflow selects that team. Repository and owner IDs come from verified GitHub evidence, because display names and clone URLs can change or be supplied by a caller.

The workflow requests an ID token for the controller's audience with permissions: id-token: write. POST /api/v1/runners/github/exchange verifies the OIDC evidence and current registration before issuing a root capability. The controller derives its team, repository, ref, SHA and workflow-run scope. User-supplied labels grant no repository or pool authority.

OIDC evidence establishes the workflow's push scope. Verified default-branch trust comes from a signed App delivery. See GitHub App and credentials.

Credential issuanceSection anchor link

The exchange issues a controller capability with nodes.claim and triggers.claim, a one-hour lifetime, and the verified repository, ref, commit workflow run, run attempt and workflow reference binding. Each repository/run/attempt can exchange once, including after the first credential is revoked. Repository identity uses GitHub's numeric ID, so a rename preserves that identity. An owner transfer requires a matching new binding.

The request takes id_token and team. The controller derives grants, push facts, expiry and runner identity. Its response carries token, team, repository, expires_at, labels and the verified binding. The token is returned once. The exchange has no automatic retry because a repeated request cannot recover a lost bearer.

POST /api/v1/runners/claim accepts the requested trigger and job kinds. Each kind requires its own claim grant, and the store checks repository and push authority before awarding work. Source, secrets, data and execution reporting use the separate capability awarded for that unit. A runner root cannot read them or a pipeline's sizing records by itself. Discovery routes remain available to live roots.

The cache-grant protocol and its issuance route are removed. Byte-storage access uses the authenticated data adapter and the unit's live authority.

Claims and executionSection anchor link

POST /api/v1/runners/claim scans one ordered queue for the requested trigger and job kinds. A mixed request needs both claim grants. Named owner and job claims use the corresponding run-scoped claim routes. Every selection checks repository authority and push scope.

Each award carries a fresh capability, a binding, granted runner identity and immutable execution inputs. The reusable Actions credential stays in the runner. SDK processes receive delegated process authority through protocol hello. Claims and exchanges never retry automatically after a lost one-time response. Lease expiry resolves an uncertain award.

The acquired checkout uses its sealed location and exact SHA. Its SDK pin selects the verified matching engine before installation, planning and execution. A workflow checkout alone cannot replace those accepted inputs.

Data and sourceSection anchor link

Data reads and writes use signed /data transfers and authenticated record operations. A live claim needs its own authorized read edges and write scope. Repository membership alone does not make every object readable. Pipeline binaries remain local to the executing machine.

Source acquisition uses the live run claim at POST /api/v1/runs/{run}/source-credential. The controller checks sealed repository authority. Actions source acquisition uses the workflow's own GITHUB_TOKEN, scoped to the registered repository. It receives no team credential or App token. See team git credentials.

Revoking a registration or credential stops later controller operations. It cannot recall GitHub tokens, provider sessions or signed URLs already issued. Set GITHUB_TOKEN to ${{ github.token }} on the runner step. The generated workflow includes this setting. The token stays in source acquisition and does not enter the pipeline environment.

WorkflowSection anchor link

Commit this workflow as .github/workflows/sparkwing.yml, replacing the team and controller URL with your registration values. The runner uses the github-actions label.

# Runs Sparkwing work for this repository on this repository's GitHub
# Actions minutes. The controller hands the job only triggers and nodes for this
# repository, and the job exits once the queue has been empty for --idle-exit.
name: sparkwing
on:
  push:
  workflow_dispatch:
permissions:
  id-token: write
  contents: read
jobs:
  sparkwing:
    runs-on: ubuntu-latest
    # The runner credential lives one hour, so the job does too.
    timeout-minutes: 60
    steps:
      - uses: actions/checkout@v4
      - name: Install sparkwing-runner
        run: |
          base=https://github.com/sparkwing-dev/sparkwing/releases/latest/download
          curl -fsSLO "$base/sparkwing-runner-linux-amd64"
          curl -fsSLO "$base/SHA256SUMS"
          grep ' sparkwing-runner-linux-amd64$' SHA256SUMS | sha256sum -c -
          install -m 0755 sparkwing-runner-linux-amd64 "$RUNNER_TEMP/sparkwing-runner"
      - name: Run this repository's Sparkwing work
        env:
          GITHUB_TOKEN: ${{ github.token }}
        run: |
          "$RUNNER_TEMP/sparkwing-runner" warm --github-actions \
            --team acme \
            --controller https://sparkwing.example.com \
            --idle-exit 2m

App-triggered runsSection anchor link

On Sparkwing Cloud an App push or pull request run goes to the Cloud launcher unless its automation or subscription selects runner: github-actions or runner: pool=<name>, or a repository pool route rule requires a pool. Each of those seals host dispatch. With github-actions, only an Actions runner whose push binding matches the delivery's repository, ref and commit may claim the run's trigger; other team machines are refused. A create-run request cannot select github-actions itself. See GitHub App run owner.

The runner requests a GitHub ID token with the controller URL as its audience, then exchanges it for a short-lived controller capability. The exchange returns the verified push binding; the runner uses that repository and expiry for its claims. The workflow needs id-token: write and GITHUB_TOKEN for source access.

warm uses controller-awarded leases and job settings. It rejects the removed --heartbeat, --lease, --holder-prefix, --trigger-source, --accept-working-tree, --cpu-ceiling, --memory-ceiling, --deadline and --warm-module flags. Configure resource ceilings on the Kubernetes launcher; configure job timeouts in the pipeline. Pipeline lint always analyzes source without running pipelines; its removed --static flag is rejected.

--max-concurrent defaults to one job. Trigger owners have separate capacity with the same bound, because an owner waits for its jobs to finish. The default runner can hold one owner and execute one job at a time.