GitHub Action
The StateAnchor GitHub Action validates your stateanchor.yaml spec, evaluates breaking changes through the gate engine, and blocks PRs that violate your API contract policy. It runs on every push and pull request to your configured branches.
Overview
On each triggering event, the action performs three steps in sequence:
- OIDC exchange — requests a GitHub OIDC token and exchanges it with StateAnchor for a short-lived action token. This authenticates the repo without storing secrets.
- Validate — sends
stateanchor.yamlto/api/action/validatefor schema and structural validation. - Gate check — calls
/api/action/gate-checkto diff the spec against the previous IR, score breaking changes, and return a gate decision.
Pull requests are always evaluated in audit mode — they report drift but never block. Only pushes to protected branches can run in sync orenforce mode, which blocks on breaking changes.
Installation
Create .github/workflows/stateanchor-sync.yml in your repository:
name: StateAnchor Sync
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
id-token: write
contents: read
jobs:
stateanchor-sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: movermon/wrapforge-action@main
id: sa
with:
mode: enforce
config-path: stateanchor.yaml
api-base-url: https://wrapforge.devInputs
| Input | Required | Default | Description |
|---|---|---|---|
mode | No | audit | Execution mode: audit (report only), sync or enforce (block on breaking changes). |
config-path | No | stateanchor.yaml | Path to the spec file relative to repo root. |
api-base-url | No | https://wrapforge.dev | StateAnchor API base URL. Do not change unless self-hosting. |
oidc-audience | No | wrapforge-sync-action | OIDC audience claim for token exchange. |
timeout-ms | No | 15000 | Request timeout in milliseconds (1000–20000). |
outage-policy | No | fail-closed | fail-closed treats service outage as a block. fail-open allows sync to pass. |
local-fallback | No | true | Run local YAML validation when remote is unavailable. |
annotate-pr | No | true | Post validation results as PR annotations. |
Required permissions
The action requires two GitHub permissions in your workflow:
| Permission | Why |
|---|---|
id-token: write | Required to request a GitHub OIDC token for authentication with StateAnchor. Without this, the action falls back to local validation only. |
contents: read | Required by actions/checkout@v4 to read the repository contents including stateanchor.yaml. |
Outputs
| Output | Type | Description | Example |
|---|---|---|---|
result | string | Overall result of the action. | pass, fail, soft-pass |
gate-action | string | Gate engine decision. | allow, approval_required, block |
gate-score | integer | Effective breaking change score. | 0, 40, 65 |
sync-run-id | UUID | ID of the sync run created by gate-check. | fb87c28a-4509-... |
mode-effective | string | Actual mode executed (may differ from requested). | audit, sync |
remote-executed | boolean | Whether remote validation ran. | true |
fallback-executed | boolean | Whether local fallback ran. | false |
Using outputs in your workflow
Reference outputs using the step ID to add conditional steps:
- uses: movermon/wrapforge-action@main
id: sa
with:
mode: enforce
- name: Comment on PR if blocked
if: steps.sa.outputs.gate-action == 'block'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '⛔ StateAnchor blocked this PR. Breaking score: ' +
'${steps.sa.outputs.gate-score}'
})
- name: Deploy if gate allows
if: steps.sa.outputs.gate-action == 'allow'
run: echo "Gate passed — deploying"Fail behavior
When gate-action is block, the action exits with a non-zero status code, causing the workflow to fail. The Actions log includes:
- The block reason (human-readable explanation)
- The effective breaking score
- A table of breaking operations with their individual scores
- The sync run ID for debugging in the StateAnchor dashboard
When outage-policy is fail-closed and the StateAnchor API is unreachable, the action also exits non-zero. With fail-open, it returns soft-pass instead.
Troubleshooting
OIDC exchange failed
The action could not obtain a GitHub OIDC token. Check that id-token: write is in your workflow permissions. Fork PRs cannot obtain OIDC tokens — the action falls back to local validation automatically.
Config not found
The spec file was not found at the configured config-path. Verify the file exists at the repo root (or the custom path) and is committed. Check for typos in the filename — it must be stateanchor.yaml, not .yml.
Gate blocked unexpectedly
Check the breaking operations table in the Actions log. If the block is intentional, you can either fix the breaking change, create a drift exception in project settings, or temporarily switch to audit mode. Review the Gate Engine docs for scoring details.
Action times out
The default timeout is 15 seconds. If the StateAnchor API is slow, increase timeout-ms up to 20000. If timeouts persist, check the outage-policy setting — fail-open will allow the workflow to pass during outages.