DidWork for GitHub Actions
A pull request is where a claimed outcome becomes permanent. Your tests prove the code does what the code does. They do not prove the deploy this branch depends on is live, the migration ran, the release published, or the endpoint the change assumes is answering.
This action runs the claims in your repo's
.didwork.yml against the systems that can
prove them, and fails the job when a required outcome is not true.
Because it fails the job, branch protection can require it — and a
merge that rests on a self-report stops being possible.
Install
# .github/workflows/verify.yml name: Verify outcomes on: pull_request permissions: contents: read pull-requests: write # only for the comment jobs: didwork: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: didworksh/verify-action@v1 with: api-key: ${{ secrets.DIDWORK_API_KEY }}
Add DIDWORK_API_KEY as a repository secret
(free key at didwork.sh/console). Without it the
gate still runs, but only http.ok claims
verify, nothing is stored, and there are no receipts to link.
The gate file
.didwork.yml at the repo root declares what
must be true to merge:
version: 1
claims:
- name: Production health
type: http.ok
expected:
url: https://example.com/health
- name: Release published
type: github.release_published
expected:
repository: acme/app
tag: v1.4.0
- name: Canary
type: http.ok
required: false # reported, cannot block
expected:
url: https://canary.example.com/health
Every claim is required unless it says
required: false. A non-required claim still
runs and still appears in the report — it just cannot fail the job,
which is how a repo adopts a new check before trusting it with the merge
button.
The file is parsed strictly. An unknown key, a malformed claim, or an
empty claims: list is an error, not something
quietly skipped — a gate that verifies nothing would report success
on everything.
What passes
| Verdict | Meaning | Required claim |
|---|---|---|
verified | the outcome is true | passes |
failed | the outcome is not true | blocks |
unknown | could not be established | blocks |
error | the check could not be run | blocks |
unknown blocking is deliberate, and it is the
decision most people want to argue with. A check that could not reach its
evidence has not shown anything to be true. Letting it through would make
this exactly the thing it exists to prevent: a green light backed by
nothing.
Exit codes, if you are wiring it yourself: 0
the gate passed, 1 a required claim is not
verified, 2 the command or the config was
wrong. A broken gate exits 2 rather than
0, because "we could not check" must never
read as "it passed".
The pull request comment
The job's pass or fail is what branch protection enforces. The comment is so a human can see why without opening the log: one row per claim, the verdict, and the evidence behind it — and, when the run had a key, a link to the receipt showing claim, evidence and verdict in full.
It updates itself. A branch pushed ten times has one comment showing the current truth, not ten showing its history.
Make it a required check
Settings → Branches → branch protection rule →
Require status checks to pass before merging, then
select the job (didwork in the example
above). The check has to have run once on a branch before GitHub will
offer it in that list.
Permissions
The comment is written with your workflow's
GITHUB_TOKEN, under whatever scope you
granted it. DidWork holds read-only credentials to the systems it
verifies and has no write access to your repository at all — it can
observe an outcome, never cause one. That is not a policy, it is the
shape of the integration: there is nowhere for repository write access to
live.
Don't want the comment? Drop
pull-requests: write and set
comment: false. The gate is unchanged.
Without Actions
The action is a thin wrapper over a CLI, so any CI runner works:
npx @didwork/inspect verify # run the gate npx @didwork/inspect verify --json # machine-readable npx @didwork/inspect verify --config ci/gate.yml
Set DIDWORK_API_KEY in the environment. CI
runs identify themselves as the ci source, so
they stay distinguishable from an agent's or a person's verifications in
your log.
Troubleshooting
Exit 2, "no gate file found". The CLI looks for
.didwork.yml,
.didwork.yaml, then
.didwork.json in the working directory. Pass
--config for anywhere else, and make sure the
job ran actions/checkout first.
The comment never appears. Three usual causes: the
workflow has no pull-requests: write
permission; the event is a push rather than a
pull_request, so there is nothing to comment
on; or the run came from a fork, where GitHub deliberately withholds
write tokens.
Everything is unknown. The
claims named systems DidWork has no read credential for. Connect them at
didwork.sh/console/providers.
Verdicts appear but receipts do not link. The run is
keyless — nothing was stored, so there is no receipt. Add the
api-key input.
Next
Give the agent the same verdicts while it works: Claude Code, Cursor, or any MCP host.