Skip to content

feat: v0.3 core CI (M8) — .gitflare/ci.yml jobs on Cloudflare Sandboxes#5

Merged
sinameraji merged 9 commits into
mainfrom
feat/v0.3-ci
Jul 17, 2026
Merged

feat: v0.3 core CI (M8) — .gitflare/ci.yml jobs on Cloudflare Sandboxes#5
sinameraji merged 9 commits into
mainfrom
feat/v0.3-ci

Conversation

@sinameraji

Copy link
Copy Markdown
Owner

What

Ships the M8 slice of v0.3 (Generic CI) from PLAN.md §4: a .gitflare/ci.yml workflow format with jobs / needs / run: steps, executed on Cloudflare Sandboxes running on the user's own account. Deploy jobs gate on test jobs and ship what CI just built rather than the stale committed file. Live logs, cancel, run history, and GitHub commit-status postback are all in.

on: push
branches: [main]
jobs:
  test:
    steps:
      - run: npm ci
      - run: npm test
  deploy:
    needs: [test]        # deploys only if tests pass
    steps:
      - cloudflare/deploy: { project: my-worker, kind: worker, entry: dist/worker.js }

How it works

  • Format — new src/ci/workflow.ts reuses the existing hand-rolled YAML subset (extended with |/|- block scalars for multi-line run:) and the shared cloudflare/deploy step validator. needs gets parse-time unknown/cycle validation + Kahn topo order; per-job env + timeout_minutes.
  • Execution — new CiDO (binding CI, migration v3). POST /run answers 202 and the pipeline runs as detached DO work under an alarm watchdog. One sandbox per run, cloned from the Artifacts mirror with env-scoped git auth (the read token never lands in argv, .git/config, or stderr; every log line is scrubbed). Jobs run in topo order with streamed, line-buffered logs over a hibernatable WebSocket.
  • Deploy jobs delegate to DeployDO (mode: "ci", strict at-sha ref-aware clone) so deploy history + rollback stay in one place across both workflow formats. Worker entries built in the sandbox are handed over via entryOverrides (gated on the deploy job's whole needs closure of run jobs succeeding).
  • CLIgitflare ci enable/disable/run/list/cancel. Enable provisions a [[containers]] block using the public docker.io/cloudflare/sandbox image (no local Docker), default instance type standard-1. Migrations v3+v4 are emitted unconditionally so cross-machine redeploys never miss an applied tag; CI_ENABLED gates behavior while the container config stays provisioned across disable.
  • Status — results post back via the legacy Commit Status API (gitflare/ci), soft-fail (GitHub being down is the whole point).

Quality

  • Adversarial pre-design review (3 lenses) before coding, then a 4-lens code review of the diff (18 findings, all verifier-confirmed). 14 fixed (watchdog/pipeline verdict races, unauthenticated cancel on public mirrors, tag-push noise, failed-build artifact leakage, dead SANDBOX fallback, skipped-deploy-reads-green, shell-metachar in remote allowlist, duplicate webhook deliveries, CONTROL_SECRET stranding); 4 documented as known limitations in PLAN §12 (all around DO eviction / GitHub webhook ordering — acceptable for a not-yet-live-validated milestone).
  • 115 unit tests (pnpm -r test), typecheck clean, worker bundle 1.57 MB / 354 KB gzip.
  • Config validated with wrangler deploy --dry-run: both the CI-provisioned and default TOML variants parse and resolve all four DO class exports, including migration v4 with an unbound Sandbox class.

Not in this PR (rest of v0.3, tracked in PLAN §12 M8 notes)

R2 build cache, Browser Run for E2E, the GitHub Actions importer, Pages build-artifact handover, runtime: worker (Dynamic Workers can't run npm), per-job container images (containers pin the image at deploy time). And the standing caveat: needs one live run against a real Workers Paid account to confirm the Containers provisioning path + Sandbox exec/gitCheckout wire behavior.

🤖 Generated with Claude Code

sinameraji and others added 9 commits July 17, 2026 02:40
- ci.yml format: jobs/needs/run steps (multi-line via new block-scalar
  support in yaml.ts), per-job env + timeout_minutes; parse-time cycle
  and unknown-needs validation, Kahn topological order
- CiDO: detached runs (202 + pending-work), one sandbox per run cloned
  from the Artifacts mirror with env-scoped git auth (token never in
  argv/.git/config/stderr; all output scrubbed), streamed live logs over
  hibernatable WS, alarm watchdog, cancel, push-storm supersede
- deploy jobs delegate to DeployDO (mode "ci", strict at-sha ref-aware
  clone) keeping one deploy history + cross-format rollback; CI-built
  worker entries ship via entryOverrides instead of stale committed files
- fail-closed: committed ci.yml with CI disabled blocks ungated deploy.yml
- GitHub commit statuses (legacy Status API, soft-fail), runs UI at
  /r/:name/ci, /control/ci/* endpoints
- CLI: gitflare ci enable/disable/run/list/cancel; containers block with
  public docker.io/cloudflare/sandbox image (no local Docker), migrations
  v3/v4 unconditional, symmetric CONTROL_SECRET reuse
- 51 new unit tests (107 total)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ifact gating)

Adversarial review of the M8 diff surfaced 18 confirmed issues; this fixes 14
and documents the rest as known limitations (PLAN §12):

- watchdog/pipeline verdict race: finalizedRunIds fence prevents a lagging
  coroutine from resurrecting a run the watchdog failed; watchdog no longer
  clears a concurrent cancel
- fallback alarm armed in begin() so pre-deadline eviction can't zombie a run
- ARTIFACTS.get moved into the try + last-resort commit status (no vanished push)
- webhook skips tag/non-branch refs (were minting failed runs + red statuses)
- artifact handover gated on the deploy job's whole run-job needs closure
  succeeding (was shipping a failed build's output)
- missing-SANDBOX path fails closed honestly (dead deploy.yml fallback removed)
- delegated-deploy response mapping extracted to a tested pure fn — skipped
  never reads as green
- remote-URL allowlist rejects shell metacharacters
- duplicate webhook deliveries de-duped by branch+sha
- dashboard cancel gated behind Access (open on public mirrors before); CLI
  cancel stays CONTROL_SECRET-gated
- CONTROL_SECRET always (re)written on enable; config persisted once the Worker
  is live (was strandable cross-machine / on partial enable)

+8 tests (delegation shapes, needs-closure, tightened remote validation); 115 total.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ry-run

wrangler deploy --dry-run flagged instance_type 'standard' as renamed to
'standard-1'. Default + allowed list updated to the numbered tiers. The
dry-run also validated both TOML variants (CI-provisioned and default) parse
and resolve RepoDO/DeployDO/CiDO/Sandbox exports — including migration v4 with
an unbound Sandbox class, clearing that live-validation TODO.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…enable error

Live run confirmed: the worker script uploads fine, but the container rollout
returns Forbidden without BOTH the Cloudchamber:Edit and Containers:Edit account
token permissions (Containers:Edit alone is insufficient — cf workers-sdk#12483).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…jection

Live run: Cloudflare's container application namespace rejects consecutive
dashes, but wrangler derives the app name as `${workerName}-sandbox` and the
worker name carries `--` from the owner--repo Artifacts convention. Emit an
explicit collapsed name in the [[containers]] block.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…proxy)

Live run: webhook sync 500'd with 'Git remote "[object JsRpcProperty]" uses
an unrecognized transport protocol'. On the current Artifacts beta, property
access on the binding (artifactsRepo.remote) returns a lazy RPC proxy that
stringifies to [object JsRpcProperty]; only RPC methods (createToken) resolve.
Thread the REPO_MAP remote string (already used by every browse path) through
RepoDO/git.push instead. Pre-existing since M1; M4's validation exercised the
one-shot import path, not the git.push webhook sync.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two issues from the live run:
- The push webhook awaited the full in-worker sync before responding, blowing
  past GitHub's 10s delivery timeout on any real repo (504 + redeliveries).
  Now respond 202 immediately and run sync + CI/deploy dispatch in waitUntil;
  sync errors surface via RepoDO/dashboard, not GitHub's delivery UI.
- CI commit-status postback soft-failed silently on an expired GITHUB_TOKEN.
  Keep the soft-fail, but log why it didn't post so it's diagnosable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sinameraji
sinameraji merged commit 1e84a7d into main Jul 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant