Experimental: This is an early release of stateful-ci. APIs, config, and behavior may change.
stateful-ci gives GitHub Actions jobs a persistent workspace across ephemeral runners.
It restores selected paths before a job starts, runs your normal CI steps, then saves the resulting workspace state for the next run. The backend runs in your own Cloudflare account.
In practice:
- The runner can still be disposable, but the workspace state does not have to be.
- You choose which paths are part of the workspace: package stores, build outputs, framework caches, generated files, browser assets, or anything else your project needs.
- Snapshots include provenance, so state from untrusted runs cannot become trusted release or deploy state.
┌───────────────────────┐
│ GitHub Actions runner │
└──────────┬────────────┘
│
│ restore
▼
┌───────────────────────┐
│ stateful-ci CLI │
└──────────┬────────────┘
│
▼
┌───────────────────────┐
│ Cloudflare Worker │
└──────────┬────────────┘
│
├── Durable Object
│ coordinates workspace state and snapshot commits
│
├── D1
│ stores runs, snapshots, metadata, and decisions
│
└── R2
stores workspace snapshot data
A run has two phases:
restore selected workspace paths
└─ run normal CI commands
└─ save selected workspace paths
└─ next run can restore from that snapshot
Start with a preset:
{
"preset": "node"
}Or choose paths directly:
{
"paths": ["node_modules", ".pnpm-store", ".turbo", ".next/cache"],
"exclude": ["coverage"]
}Initialize it in your repo:
bunx stateful-ci initDeploy the backend:
bunx stateful-ci deployUse it in GitHub Actions:
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: eersnington/stateful-ci@v1
with:
command: restore
- run: bun install
- run: bun test
- uses: eersnington/stateful-ci@v1
if: always()
with:
command: saveInspect local state and published CI runs with TUI (OpenTUI):
bunx stateful-cistateful-ci has three main pieces:
| Piece | Role |
|---|---|
stateful-ci CLI |
Runs locally and inside GitHub Actions. Restores, saves, deploys, and opens the TUI dashboard. |
| Cloudflare Worker | Receives restore/save requests and routes workspace operations. |
| Durable Object + D1 + R2 | Coordinates snapshot state, records metadata, and stores workspace data. |
The Cloudflare backend is deployed to your own account. There is no hosted service required.
Persistent workspace state needs provenance.
stateful-ci records where each snapshot came from and separates state by trust boundary.
trusted branch snapshot
├─ can seed trusted jobs
└─ can seed pull request jobs
untrusted pull request snapshot
├─ can be reused by that pull request
└─ cannot become state for trusted branches, releases, or deploy jobs
The goal is workspace continuity without turning persistent state into a cache-poisoning path.
Apache-2.0