{"id":50482323,"url":"https://github.com/eersnington/stateful-ci","last_synced_at":"2026-06-01T18:31:03.423Z","repository":{"id":358081017,"uuid":"1237611415","full_name":"eersnington/stateful-ci","owner":"eersnington","description":"Persistent CI workspaces for GitHub Actions. Restore, run, and save build state across ephemeral runners using your own Cloudflare backend.","archived":false,"fork":false,"pushed_at":"2026-06-01T12:39:15.000Z","size":746,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-01T14:22:37.940Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eersnington.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-13T10:44:01.000Z","updated_at":"2026-05-30T13:58:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/eersnington/stateful-ci","commit_stats":null,"previous_names":["eersnington/stateful-ci"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/eersnington/stateful-ci","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eersnington%2Fstateful-ci","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eersnington%2Fstateful-ci/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eersnington%2Fstateful-ci/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eersnington%2Fstateful-ci/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eersnington","download_url":"https://codeload.github.com/eersnington/stateful-ci/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eersnington%2Fstateful-ci/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33789013,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-01T02:00:06.963Z","response_time":115,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-01T18:31:02.283Z","updated_at":"2026-06-01T18:31:03.418Z","avatar_url":"https://github.com/eersnington.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# stateful-ci\n\n![Status](https://img.shields.io/badge/status-early%20development-f59e0b?style=flat-square)\n\n\u003e Experimental: This is an early release of stateful-ci. APIs, config, and behavior may change.\n\nstateful-ci gives GitHub Actions jobs a persistent workspace across ephemeral runners.\n\nIt 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.\n\nIn practice:\n\n- The runner can still be disposable, but the workspace state does not have to be.\n- 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.\n- Snapshots include provenance, so state from untrusted runs cannot become trusted release or deploy state.\n\n## How it works\n\n```\n┌───────────────────────┐\n│ GitHub Actions runner │\n└──────────┬────────────┘\n           │\n           │ restore\n           ▼\n┌───────────────────────┐\n│   stateful-ci CLI     │\n└──────────┬────────────┘\n           │\n           ▼\n┌───────────────────────┐\n│  Cloudflare Worker    │\n└──────────┬────────────┘\n           │\n           ├── Durable Object\n           │   coordinates workspace state and snapshot commits\n           │\n           ├── D1\n           │   stores runs, snapshots, metadata, and decisions\n           │\n           └── R2\n               stores workspace snapshot data\n```\n\nA run has two phases:\n\n```\nrestore selected workspace paths\n└─ run normal CI commands\n   └─ save selected workspace paths\n      └─ next run can restore from that snapshot\n```\n\n## Configuration\n\nStart with a preset:\n\n```json\n{\n  \"preset\": \"node\"\n}\n```\n\nOr choose paths directly:\n\n```json\n{\n  \"paths\": [\"node_modules\", \".pnpm-store\", \".turbo\", \".next/cache\"],\n  \"exclude\": [\"coverage\"]\n}\n```\n\n## Usage\n\nInitialize it in your repo:\n\n```bash\nbunx stateful-ci init\n```\n\nDeploy the backend:\n\n```bash\nbunx stateful-ci deploy\n```\n\nUse it in GitHub Actions:\n\n```yaml\npermissions:\n  contents: read\n  id-token: write\n\nsteps:\n  - uses: actions/checkout@v4\n\n  - uses: eersnington/stateful-ci@v1\n    with:\n      command: restore\n\n  - run: bun install\n  - run: bun test\n\n  - uses: eersnington/stateful-ci@v1\n    if: always()\n    with:\n      command: save\n```\n\nInspect local state and published CI runs with TUI (OpenTUI):\n\n```bash\nbunx stateful-ci\n```\n\n## Architecture\n\nstateful-ci has three main pieces:\n\n| Piece                    | Role                                                                                           |\n| ------------------------ | ---------------------------------------------------------------------------------------------- |\n| `stateful-ci` CLI        | Runs locally and inside GitHub Actions. Restores, saves, deploys, and opens the TUI dashboard. |\n| Cloudflare Worker        | Receives restore/save requests and routes workspace operations.                                |\n| Durable Object + D1 + R2 | Coordinates snapshot state, records metadata, and stores workspace data.                       |\n\nThe Cloudflare backend is deployed to your own account. There is no hosted service required.\n\n## Security model\n\nPersistent workspace state needs provenance.\n\nstateful-ci records where each snapshot came from and separates state by trust boundary.\n\n```\ntrusted branch snapshot\n├─ can seed trusted jobs\n└─ can seed pull request jobs\n\nuntrusted pull request snapshot\n├─ can be reused by that pull request\n└─ cannot become state for trusted branches, releases, or deploy jobs\n```\n\nThe goal is workspace continuity without turning persistent state into a cache-poisoning path.\n\n## License\n\nApache-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feersnington%2Fstateful-ci","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feersnington%2Fstateful-ci","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feersnington%2Fstateful-ci/lists"}