Stop paying CI to test the same code twice
Coding agents push every change to staging and then to production, so CI tests and builds the same files twice. hushrun validates each change once and ships staging's exact artifact to production, by digest.
Free to start, with GitHub or Google. In a pilot, we set it up with you in one session.
Or run this at your repository's root:
npx hushrun initIt finds your checks, signs this terminal in, and shows every change before making it.
sha256:bad90344…28f80e5399f- planfull
- lintran
- unitran
- integrationran
- buildran
- deployran
imagesha256:3cae5d5c…b157
6083e16- planpromote
- lintreused
- unitreused
- integrationreused
- buildcopied
- deployran
imagesha256:3cae5d5c…b157staging's, by digest
What's true today
- Runs in production on
Kobranzapp/back, as ci-promote - Free CLI, for macOS, Linux and Windows
- Every event signed and verified by the CLI
- GitHub Actions today, GitLab next
Your CI bill pays for the same work twice
Agents push every change to staging, then the same change to production. The files are identical, but the production pipeline can't tell: it installs, tests and builds from scratch.
sha256:bad90344…28f80e5399f- lintran
- unitran
- integrationran
- buildran
- deployran
imagesha256:3cae5d5c…b157
6083e16- lintagain
- unitagain
- integrationagain
- buildagain
- deployran
imagerebuilt: a new digest
Every change, two full runs
With staging and production, one pipeline run in two re-validates files that already passed.
Cheaper minutes don't fix it
Lower runner prices cut the price of a minute, not the number of minutes your agents trigger.
More pushes, more waste
Every agent push, re-push and rebase pays for its duplicate run again.
Build and validate once. Promote the exact artifact.
hushrun is one static binary that runs inside the CI you already have. It adds three things to your pipelines:
A content digest
It hashes the files of each deployable scope into a source digest, independent of commits, branches and the VCS. A merge commit with the same files has the same digest.
acme/api
staging develop 0e5399fsha256:bad90344…28f8production main 6083e16sha256:bad90344…28f8same files, same digest
Signed evidence in an append-only log
Each pipeline records what it did (checks run, artifacts built, deploys) as signed events, in an append-only log we host.
evidence · acme/api
sha256:bad90344…28f801M63WSKM1…started staging ci 01M63WSKS5…check staging ci 01M63WSKX1…check staging ci 01M63WSM0P…check staging ci 01M63WSM60…artifact staging ci 01M63WSMB5…deployed staging ci 01M63WSMMM…passed staging ci A plan before any work
Before running anything, a stage asks the planner what's left for this digest. When staging already validated it, production copies staging's artifact by digest and deploys it.
production · push to main $ hushrun plan --stage production default promote: every requirement of `production` is met: deploying image us-docker.pkg.dev/acme-staging/api/server@sha256:3cae5d5c…b157 by digest image: us-docker.pkg.dev/acme-staging/api/server@sha256:3cae5d5c…b157 requirement lint satisfied requirement unit satisfied requirement integration satisfied
Real hushrun planoutput from a demo repository. Digests are shortened; hover for the full value.
In the demo, develop at 0e5399f and main at 6083e16 are different commits with the same files, so both have the source digest sha256:bad90344…28f8.
Six plan modes, and nothing in between
Every plan ends in exactly one mode. Pick one to see what hushrun plan printed for it on the demo repository.
$ hushrun plan --stage production default promote: every requirement of `production` is met: deploying image us-docker.pkg.dev/acme-staging/api/server@sha256:3cae5d5c…b157 by digest image: us-docker.pkg.dev/acme-staging/api/server@sha256:3cae5d5c…b157 requirement lint satisfied requirement unit satisfied requirement integration satisfied
hushrun plan: production
| Scope | Mode | Reason |
|---|---|---|
| default | promote | every requirement of production is met: deploying image us-docker.pkg.dev/acme-staging/api/server@sha256:3cae5d5c…b157 by digest |
$ hushrun plan --stage production default refresh: the artifacts are reusable: deploying image us-docker.pkg.dev/acme-staging/api/server@sha256:3cae5d5c…b157 by digest; running integration (expired) first checks to run: integration image: us-docker.pkg.dev/acme-staging/api/server@sha256:3cae5d5c…b157 requirement lint satisfied requirement unit satisfied requirement integration expired
hushrun plan: production
| Scope | Mode | Reason |
|---|---|---|
| default | refresh | the artifacts are reusable: deploying image us-docker.pkg.dev/acme-staging/api/server@sha256:3cae5d5c…b157 by digest; running integration (expired) first |
$ hushrun plan --stage staging default full: no reusable `image` (none was recorded): building and running lint (missing), unit (missing), integration (missing) checks to run: lint unit integration requirement lint missing requirement unit missing requirement integration missing
hushrun plan: staging
| Scope | Mode | Reason |
|---|---|---|
| default | full | no reusable image (none was recorded): building and running lint (missing), unit (missing), integration (missing) |
$ hushrun plan --stage production default wait: `staging` is still running sha256:bad90344f9e5 in github run 201 attempt 1 (https://github.com/acme/api/actions/runs/201/attempts/1); its completion triggers a new plan here requirement lint missing requirement unit missing requirement integration missing
hushrun plan: production
| Scope | Mode | Reason |
|---|---|---|
| default | wait | staging is still running sha256:bad90344f9e5 in github run 201 attempt 1 (https://github.com/acme/api/actions/runs/201/attempts/1); its completion triggers a new plan here |
$ hushrun plan --stage production default skip: `production` already runs sha256:bad90344f9e5 (deployed at 2026-10-29T13:15:17.142Z); re-run, or run manually, to deploy it again requirement lint satisfied requirement unit satisfied requirement integration satisfied
hushrun plan: production
| Scope | Mode | Reason |
|---|---|---|
| default | skip | production already runs sha256:bad90344f9e5 (deployed at 2026-10-29T13:15:17.142Z); re-run, or run manually, to deploy it again |
$ hushrun plan --stage production default blocked: `staging` failed sha256:bad90344f9e5 in github run 201 attempt 1 (https://github.com/acme/api/actions/runs/201/attempts/1); fix forward, or force requirement lint satisfied requirement unit failed requirement integration missing
hushrun plan: production
| Scope | Mode | Reason |
|---|---|---|
| default | blocked | staging failed sha256:bad90344f9e5 in github run 201 attempt 1 (https://github.com/acme/api/actions/runs/201/attempts/1); fix forward, or force |
Already running in production, on our own CI
hushrun generalizes ci-promote, a bash tool that does the same for one GitHub repository. ci-promote runs in production on Kobranzapp/back, the backend of our own company, Kobranzapp.
Since June, every commit to main on Kobranzapp/back had files that staging had already tested and built. Its production runs skip the tests and the build, and copy staging's exact image.
That's one team, and it's ours. Pilots are how we find out whether yours looks the same.
version: 1 repository: github.com/acme/api checks: lint: { kind: source, run: pnpm lint } unit: { kind: source, run: pnpm test, timeout: 20m } integration: { kind: source, run: pnpm test:integration, timeout: 30m } artifacts: image: type: oci locations: staging: us-docker.pkg.dev/acme-staging/api/server production: us-docker.pkg.dev/acme-prod/api/server stages: staging: branch: develop require: [lint, unit, integration] production: branch: main after: staging require: [lint, unit, integration]
hushrun.yaml for a two-stage setup. The CLI validates it.- id: plan run: hushrun plan --stage production --start # later jobs carry the plan out instead of deciding again deploy: if: contains(fromJSON('["promote","refresh","full"]'), needs.plan.outputs.mode)
Cheaper minutes. Many more runs.
The price of a CI minute went down this year. The number of minutes didn't.
- −39%
GitHub cut hosted-runner prices by up to 39% on January 1, 2026.
- +130%
Tian Pan describes a CI line item that rose 130% in six weeks once coding agents ran CI on every iteration.
- Jun 1
Copilot code review has consumed GitHub Actions minutes on private repositories since June 1, 2026.
Agents don't only write more code; they run more CI. hushrun makes each run count once.
Nothing is skipped without signed evidence
hushrun decides from evidence it can verify, and fails closed when it can't.
Signed events
Every check result, artifact and deploy is a signed DSSE envelope. In GitHub Actions, CI signs as the repository's own workflow, with no key to store; a signed-in laptop signs with the key its sign-in registered.
Verified on every read
Membership is the policy: an event counts when a signed-in member's machine, one of your org's agents, or the repository's own CI signed it. Remove someone, and what they sign from then on stops counting. In the dashboard, a stage can take evidence from CI only. An event the CLI can't verify counts as missing.
Fails closed
Missing, expired or failed evidence means the checks run (
refresh) or the stage builds (full). Approvals are never waived, and you can force a full run.Append-only, in standard formats
The log lets CI add events, never rewrite or delete them, and logs every write. Its checkpoints are signed notes, the format of Go's checksum database: Go's
notepackage verifies them. Its proofs follow RFC 6962. The hosted service says who your members are; the deploy decision is always the CLI's, in your CI.
$ hushrun verify --scope default --digest sha256:bad90344…28f8 ok v1/events/default/bad90344f9e5f7d8a3f4a46a99b40eede644f97d1dc93de3ce59c7cbb9ee28f8/01M63WSKM1RC1PA3Y6HHBTFNFC.started.staging.json: started at staging signed by ci (custody ci) for sha256:bad90344…28f8, with identity https://github.com/acme/api/.github/workflows/deploy-staging.yml@refs/heads/develop, timestamped at 2026-10-29T13:15:08.802Z ok v1/events/default/bad90344f9e5f7d8a3f4a46a99b40eede644f97d1dc93de3ce59c7cbb9ee28f8/01M63WSKS5VZAK8574WJTKDWS8.check.staging.json: check at staging signed by ci (custody ci) for sha256:bad90344…28f8, with identity https://github.com/acme/api/.github/workflows/deploy-staging.yml@refs/heads/develop, timestamped at 2026-10-29T13:15:08.966Z ok v1/events/default/bad90344f9e5f7d8a3f4a46a99b40eede644f97d1dc93de3ce59c7cbb9ee28f8/01M63WSKX146X9HF9E43DHN0WK.check.staging.json: check at staging signed by ci (custody ci) for sha256:bad90344…28f8, with identity https://github.com/acme/api/.github/workflows/deploy-staging.yml@refs/heads/develop, timestamped at 2026-10-29T13:15:09.089Z ok v1/events/default/bad90344f9e5f7d8a3f4a46a99b40eede644f97d1dc93de3ce59c7cbb9ee28f8/01M63WSM0PNGDC8NSK7J079SWY.check.staging.json: check at staging signed by ci (custody ci) for sha256:bad90344…28f8, with identity https://github.com/acme/api/.github/workflows/deploy-staging.yml@refs/heads/develop, timestamped at 2026-10-29T13:15:09.206Z ok v1/events/default/bad90344f9e5f7d8a3f4a46a99b40eede644f97d1dc93de3ce59c7cbb9ee28f8/01M63WSM60NR3SEF83MRHGNN0H.artifact.staging.json: artifact at staging signed by ci (custody ci) for sha256:bad90344…28f8, with identity https://github.com/acme/api/.github/workflows/deploy-staging.yml@refs/heads/develop, timestamped at 2026-10-29T13:15:09.377Z ok v1/events/default/bad90344f9e5f7d8a3f4a46a99b40eede644f97d1dc93de3ce59c7cbb9ee28f8/01M63WSMB5412BRQPQC8HX8GR3.deployed.staging.json: deployed at staging signed by ci (custody ci) for sha256:bad90344…28f8, with identity https://github.com/acme/api/.github/workflows/deploy-staging.yml@refs/heads/develop, timestamped at 2026-10-29T13:15:09.542Z ok v1/events/default/bad90344f9e5f7d8a3f4a46a99b40eede644f97d1dc93de3ce59c7cbb9ee28f8/01M63WSMMMD5NS6JX3DZJMT0AJ.passed.staging.json: passed at staging signed by ci (custody ci) for sha256:bad90344…28f8, with identity https://github.com/acme/api/.github/workflows/deploy-staging.yml@refs/heads/develop, timestamped at 2026-10-29T13:15:09.845Z
hushrun verify on the demo's production run: who signed what, for which digest.Fits the CI you already run
- CI
- GitHub Actions
- A setup action
- Ready-made workflows
- Step summaries and annotations
- GitLab CI, next
- Evidence
- Hosted, over HTTPS
- GitHub OIDC for CI
- Tokens for any other CI
- A transparency log of every write
- Artifacts
- OCI images, by digest
- Multi-platform indexes
- Plain files
- Code
- Any language
- Any VCS
- Any directory
- One static binary
One requirement: a build that can run in every environment, with environment values supplied at deploy time instead of baked in.
A free CLI. A paid plan for teams.
CLI
Free
Everything that decides a deploy, in your CI.
- Source digests for any language and any VCS
- Signed events, verified on every read
- The planner and its six modes
- OCI images promoted by digest
- Decisions made in your CI, never by our servers
Team plan
Free to start
What a team shares around the CLI. It never makes a deploy decision.
- A hosted log, with tokens and GitHub OIDC for CI
- A dashboard of every stage, digest and deploy
- An audit trail of who signed what, verified by the same code as the CLI
Prices are on the pricing page: free for 2,000 recorded checks a month. Pilot teams get a founding price, and the CLI stays free.
Questions teams ask first
hushrun skips a check only when it has valid evidence for exactly the same files: a pass, signed by a member's machine or your repository's CI (or only CI, for a stage set that way in the dashboard), younger than the check's
max_age. A repository can also have CI reuse only CI's results. Deployment checks, such as smoke tests against production, still run after the deploy. Approvals are never waived, and you can force a full run.Promotion moves one artifact through every environment, so it fits builds that read their configuration at deploy or run time: environment variables, secrets, config files. If yours bakes environment values in, production builds its own artifact (mode
full) and still reuses the check evidence your policy accepts. Pilots start by checking this together.The CLI is free. The hosted plan for teams (a shared evidence log, policy, an audit trail and a dashboard) is free for 2,000 recorded checks a month; Team is $49 a month and Growth $99, and skips are always free. The pricing page has the rest. Pilot teams get a founding price, and pilots are free while they run.
Caches such as Nx, Turborepo or Bazel replay task results, and path filters skip jobs by path; production still produces an artifact of its own. hushrun deploys the artifact staging validated, identified by digest, with a signed record of the checks it passed. It works alongside your caches.
Events: digests, check names and results, artifact references and run links. Not your source code, and not your secrets. The CLI works without it.
If coding agents push to your repositories and CI costs US$300 or more a month, Camilo, who builds hushrun, will invite you to a 30-minute call and, if it fits, a free four-week pilot on one repository, set up together. Otherwise, we'll write when a pilot fits your team. Either way, you can start free today.
hushrun skips a check only when it has valid evidence for exactly the same files: a pass, signed by a member's machine or your repository's CI (or only CI, for a stage set that way in the dashboard), younger than the check's max_age. A repository can also have CI reuse only CI's results. Deployment checks, such as smoke tests against production, still run after the deploy. Approvals are never waived, and you can force a full run.
Try it on one repository
Sign up with GitHub or Google, then run one command at your repository's root.
Or set it up with us
We also work with a few pilot teams, on one repository each.
Setup together, in one session. Free during a four-week pilot. A founding price afterwards.