Recipes
Six places software reports success and the outcome can still be
false. Each recipe is one claim — or one pair — in the
dialect that workflow actually uses. Every recipe also runs as a
plain curl, in TypeScript, or in Python;
the quickstart shows the translation.
Verify before you report done
The agent says "done, PR merged, CI green." It graded its own work — the PR is closed, not merged, and the workflow is still running. Give the agent a verifier that isn't itself:
claude mcp add didwork -e DIDWORK_API_KEY=dk_your_key -- npx -y @didwork/mcp
Then one rule in CLAUDE.md, .cursorrules, or the agent's system prompt:
Before reporting a task complete, verify each outcome you claim with did_verify. Report done only on VERIFIED. Surface FAILED and UNKNOWN instead of retrying silently.
The rule survives every session — the agent stops asserting outcomes and starts checking them. Config for Cursor and Claude Desktop is in the docs.
Done means deployed
The deploy job exited 0. That proves the job ran — not that your commit is what production is serving. Two claims close the gap: the SHA is an ancestor of the deploy branch, and the live site answers.
# .github/workflows/deploy.yml — after the deploy step - name: Verify the deploy actually landed run: | curl -sf https://api.didwork.sh/v1/verify \ -H "authorization: Bearer $DIDWORK_API_KEY" \ -H "x-didwork-client: ci" \ -H "content-type: application/json" \ -d '{"type":"github.commit_in_branch","expected":{"repository":"you/app","commit":"'"$GITHUB_SHA"'","branch":"production"}}' \ | jq -e '.status == "verified"' curl -sf https://api.didwork.sh/v1/verify \ -H "authorization: Bearer $DIDWORK_API_KEY" \ -H "x-didwork-client: ci" \ -H "content-type: application/json" \ -d '{"type":"http.ok","expected":{"url":"https://your.app/health"}}' \ | jq -e '.status == "verified"' env: DIDWORK_API_KEY: ${{ secrets.DIDWORK_API_KEY }}
The step fails the run on FAILED
and UNKNOWN — a deploy
you can't prove is not a deploy you announce. The step is committed,
so every teammate sees the verdict in every run.
Cancelled means locked out
Stripe returned 200 on the cancellation. The subscription might be
scheduled to cancel at period end — and the customer might
still be inside. Verify the record and the access it
controls; an expected 401 is a real
outcome.
const cancelled = await did.verify({ type: "stripe.subscription_cancelled", expected: { subscription: sub.id } }); const lockedOut = await did.verify({ type: "http.ok", expected: { url: `https://your.app/api/me?user=${userId}`, status: 401 } }); if (cancelled.status !== "verified" || lockedOut.status !== "verified") escalate();
stripe.subscription_cancelled fails on
scheduled-at-period-end by design — "will cancel" is not
"cancelled".
Refund before reply
The support automation issued a refund and is about to tell the customer. The API call succeeded; the refund can still be pending, for the wrong amount, or a duplicate. Verify before the send, not after the complaint:
verification = did.verify( type="stripe.refund", expected={"payment": payment_id, "amount": 4999}, ) if verification.status == "verified": reply("Your refund is through.") else: escalate(verification) # pending refund is UNKNOWN, not verified
The claim checks existence, success, amount, currency, and
duplicates. A pending refund resolves
UNKNOWN — tell the customer
"processing", not "done".
Resolved means it stopped
The incident is marked resolved in Sentry. Resolved is a state someone set; stopped is a fact about production. Check the state once, then watch the fact:
await did.verify({ type: "sentry.issue_resolved", expected: { issue: "PROJ-123" } }); // re-verifies every hour; your webhook hears the moment it regresses await did.watch({ type: "sentry.no_new_events_since", expected: { issue: "PROJ-123", since: resolvedAt }, every: "1h", webhook_url: "https://your.app/hooks/didwork" });
sentry.issue_resolved fails on ignored
issues and carries ISSUE_REGRESSED when
the issue came back. On Pro, a watch turning
FAILED also emails you.
Done means merged
The ticket moved to Done. The PR it names is closed — not merged. Cross-check the tracker's word against the repository's:
issue = did.verify(type="linear.issue_completed", expected={"issue": "ENG-421"}) merged = did.verify( type="github.pr_merged", expected={"repository": "you/app", "pull_request": 1834}, ) if issue.status == "verified" and merged.status != "verified": reopen("ENG-421", reason=merged.reason) # e.g. PR_CLOSED_NOT_MERGED
linear.issue_completed counts completed
states only — cancelled is not resolved. Jira's
jira.issue_done does the same through
the Done status category, whatever your workflow names it.