Skip to content

Evidence from agents and laptops

A coding agent, or a person, can run a stage's checks before pushing, in the image CI runs them in, on exactly the files about to be committed, and sign what happened. CI's plan then counts that evidence and skips those checks.

To skip checks with hushrun run -- COMMAND alone, an agent token is all a sandbox needs: see Agents in sandboxes. This page is about promotion, and assumes the pipeline is set up (Promotion).

How it works

hushrun run --all --stage staging --isolated --worktree
  • --worktree digests the files as git add -A && git commit would record them now: the evidence is about the commit about to be made.
  • --all runs every source check the stage requires that has no valid evidence yet.
  • --isolated runs each check in its pinned image and platform, with Docker or Podman, on an export of exactly the digested files. The check gets no network unless it declares one, and only the variables its env names: never a secret, and never the signing key, which stays with hushrun.
  • The evidence records custody isolated, with the image, platform and network it ran with. CI's plan counts it at every stage that isn't CI-only.

Then commit exactly what was validated, with no other change, and push. If the files differ from what ran, the digest differs, and CI runs the checks itself.

Pin the images CI runs checks in

An isolated run needs the check's image, pinned by digest, and its platform. A check that pins them counts evidence only from that image and platform, so pin the image your CI job already uses:

hushrun.yaml
checks:
  unit:
    kind: source
    run: go test ./...
    image: golang@sha256:<digest of the image CI uses>
    platform: linux/amd64
    env: [GOFLAGS]   # the only variables the check sees
    network: none    # the default
  • network is none by default. open is for a check that downloads. restricted runs the check on the Docker or Podman network that HUSHRUN_RESTRICTED_NETWORK names, one whose egress you restrict.
  • CI must run the check in the same image. With the reusable workflow on GitHub, set isolated: true: its test job then runs the plan's checks with hushrun run --isolated too. Otherwise run the CI job in that image, and pass it to hushrun run --stage as --environment-image <image>@sha256:….
  • A check whose env names a secret never runs isolated: keep it in CI. hushrun refuses any name that contains TOKEN, SECRET, PASSWORD, PASSWD, PASSPHRASE, CREDENTIAL, PRIVATE, AUTH, ACCESS_KEY, APIKEY, DATABASE_URL or _KEY_, ends in _KEY, _PAT, _DSN or _PWD, or is one of a few more, such as GOOGLE_APPLICATION_CREDENTIALS, REDIS_URL and SSH_AUTH_SOCK. The list is a safety net: name in env only what the check needs.

Give each agent a key

An agent signs with the machine key its agent token came with. Its events count because the agent is one of the org's signers: nothing goes in hushrun.yaml. An org admin makes the token, which always comes with its key, in the repository or on the dashboard's Agents page:

hushrun token create --agent claude-code

It prints the token, hra_…, and the machine key, once. Every sandbox of the agent signs as that one machine. Give each sandbox:

VariableValue
HUSHRUN_TOKENThe agent token
HUSHRUN_MACHINE_KEYThe machine key: the whole PEM block, as one value. Or HUSHRUN_MACHINE_KEY_FILE, the path of a file outside the repository that holds it
HUSHRUN_PROVIDERlocal, in a sandbox that sets CI=true, so that hushrun doesn't take it for CI
HUSHRUN_INVOKED_BYOptional: agent:<name>, which events record. Nothing decides on it

The sandbox also needs Docker or Podman. HUSHRUN_CONTAINER_RUNTIME picks one, or names another Docker-compatible CLI. Exports go under hushrun's cache directory, which Docker Desktop, Colima and Podman machines share with their VMs; HUSHRUN_EXPORT_DIR moves them.

  • The token lasts 30 days unless hushrun token create --expires says otherwise, 365 days at most. The same token serves hushrun run -- COMMAND's results (Agents in sandboxes).
  • The key never goes in the repository: a key there would be part of what --worktree digests and the agent commits. hushrun refuses a key file inside the repository, and an isolated run refuses to export one.
  • Revoking the token, with hushrun token revoke claude-code or in the dashboard (Agent tokens), stops what the agent signs from then on from counting. What it signed before keeps counting.
  • HUSHRUN_SIGNING=machine hushrun doctor says whether hushrun's server counts the machine, and as whose.

People's machines

A teammate's machine counts the same way, with no token to make. hushrun login registers the machine it runs on, with a key hushrun makes the first time and keeps beside its credentials, never in a repository. Its events count while its owner is a member of the org, at every stage that isn't CI-only.

  • On a signed-in machine, hushrun signs with that key by default, but never in CI: there it signs with a machine key only when HUSHRUN_MACHINE_KEY or HUSHRUN_MACHINE_KEY_FILE gives one, so a person's sign-in left on a runner never signs a pipeline's events as that person.
  • hushrun whoami shows the machine's key id.
  • hushrun logout revokes the sign-in and the machine with it. What it signed before keeps counting.
  • A machine unused for 90 days is revoked, and hushrun login on it brings it back. A person has at most 20 machines in an org.
  • Checks that pin no image can also run on the machine itself, with hushrun run --all --stage staging --worktree: that evidence records custody local.

The organization's machines, and how to revoke one, are in Machines.

Keep a stage to CI's evidence

A stage that should never count an agent's or a laptop's evidence, such as production, is made CI-only in the dashboard: for every repository of the org on the org's Settings page, or for one repository on its repository page. It isn't in hushrun.yaml, so no pull request, an agent's included, can drop it.

  • At a CI-only stage, checks, artifacts and lifecycle events count only when the repository's CI signed them. Approvals still come from people.
  • Evidence others signed before the stage became CI-only keeps counting there; evidence signed while it was CI-only never does.
  • A GitLab pipeline signs as an agent, so don't make a GitLab project's stages CI-only (GitLab CI).

More in CI-only stages.

Tell the agent

Add this to AGENTS.md, or the file your agent reads, and adjust the stage:

## Before pushing

Run `hushrun run --all --stage staging --isolated --worktree`. It runs the missing checks in the
pinned CI image on exactly the content you are about to commit, and records signed evidence.
Commit everything it validated, with no other changes, then push. CI then skips those checks.

Approvals

A stage that requires an approval check waits until someone approves the digest it would deploy. Until then its plan says blocked, and names the approval:

hushrun.yaml
checks:
  release: { kind: approval }

stages:
  production:
    branch: main
    after: staging
    require: [lint, unit, integration, release]

From your own machine, signed in with hushrun login:

hushrun approve release --stage production --comment "QA signed off"
  • It shows what you approve, the digest and its revision, what the stage runs now, and the git log range between the two. Then it signs with your machine's key, and records the approval.
  • It checks first that the approval would count. If it wouldn't, it records nothing, and says why.
  • Approvals count only from people. hushrun approve refuses to run in CI or as an agent, and a plan never counts an approval the CI or an agent signed, at any stage: no pipeline or agent approves its own deploy.
  • --out approval.json writes the signed approval to a file instead of recording it, to upload in the dashboard.

In the dashboard, an approval is given on the digest's review page, reached from the overview's "waits for approval" and from the digest's page. Approving there registers your browser's key as your machine.

Then run the stage's plan again, such as by re-running its workflow: it deploys. A forced run never waives an approval.

When something is refused

MessageWhat to do
pins no `image`, so it can't run isolatedPin the check's image, by digest (above)
an isolated run never passes secrets inKeep that check in CI, or drop the variable from its env
declares `network: restricted`, which hushrun can't provideSet HUSHRUN_RESTRICTED_NETWORK to a network whose egress you restrict
isn't on PATH: install Docker or PodmanInstall one, or set HUSHRUN_CONTAINER_RUNTIME
collides with another digested pathTwo paths differ only in case: set HUSHRUN_EXPORT_DIR to a case-sensitive directory
here digests to …, not …The checkout isn't the content named: run where it is, with --worktree for uncommitted changes
a machine's key must never be committedKeep the key in hushrun's configuration directory, or set HUSHRUN_MACHINE_KEY_FILE outside the repository
this command signs eventsSign in with hushrun login, or set HUSHRUN_MACHINE_KEY
no valid signature by a machine of a member or an agent of the orgSign in with hushrun login, or give the agent the key its token came with, in HUSHRUN_MACHINE_KEY
this approval wouldn't countApprove from a machine registered with hushrun login
approvals count only from peopleAn agent or the CI tried to approve: a member approves on their own machine with hushrun approve, or in the dashboard
Markdown