Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

Give agent sandboxes time to start after a deploy (#22)

The first sandbox on a host after a deploy pulls the runner image, which
takes longer than the containers library's 8 s and 20 s defaults, so
creating a sandbox returned 500. Wait up to three minutes, and if the
container still doesn't start, mark the sandbox failed and return 503.


Claude-Session: https://claude.ai/code/session_01AizKXSsengXZRegJxRATkL

Co-authored-by: Claude <noreply@anthropic.com>
Canberk Demirci authored · GitHub committed signed1 parent d10e3dbcommit 388e5b48f001fd71fe0e030777e412f1fa783ae3Browse files
3 files changed+26−13
modified.github/workflows/live-test.yml+1−1View file
@@ -283,7 +283,7 @@
283283 run: |
284284 set -euo pipefail
285285 base="$DIFFINERY/api/v3/repos/$OWNER/$REPO/diffinery/sandboxes"
286- id=$(curl -fsS -X POST "$base" -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" --data '{}' | jq -r .id)
286+ id=$(curl -fsS --retry 1 --retry-delay 15 -X POST "$base" -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" --data '{}' | jq -r .id)
287287 echo "sandbox $id"
288288 for i in $(seq 1 40); do
289289 out=$(curl -sS -X POST "$base/$id/exec" -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" --data '{"cmd":"cat README.md && git lfs ls-files | wc -l"}' || true)
modifiedapps/diffinery/src/repo/actions.ts+18−11View file
@@ -1310,17 +1310,24 @@
13101310 const secret = randomToken(32);
13111311 this.sql.exec("INSERT INTO sandboxes (id, secret, created_by, ref, created_at, status) VALUES (?, ?, ?, ?, ?, 'running')", id, await sha256(token), JSON.stringify(caller), ref ?? m.defaultBranch, now());
13121312 const stub = this.env.SANDBOX.get(this.env.SANDBOX.idFromName(id));
1313- await stub.boot({
1314- DIFFINERY_CLONE_URL: `${this.env.PUBLIC_URL}/${m.owner}/${m.name}.git`,
1315- DIFFINERY_REF: ref ?? m.defaultBranch,
1316- DIFFINERY_JOB_TOKEN: token,
1317- DIFFINERY_SANDBOX_SECRET: secret,
1318- DIFFINERY_API_URL: this.env.PUBLIC_URL,
1319- GIT_AUTHOR_NAME: caller.login,
1320- GIT_AUTHOR_EMAIL: `${caller.login}@users.noreply.diffinery.com`,
1321- GIT_COMMITTER_NAME: caller.login,
1322- GIT_COMMITTER_EMAIL: `${caller.login}@users.noreply.diffinery.com`,
1323- }, secret);
1313+ try {
1314+ await stub.boot({
1315+ DIFFINERY_CLONE_URL: `${this.env.PUBLIC_URL}/${m.owner}/${m.name}.git`,
1316+ DIFFINERY_REF: ref ?? m.defaultBranch,
1317+ DIFFINERY_JOB_TOKEN: token,
1318+ DIFFINERY_SANDBOX_SECRET: secret,
1319+ DIFFINERY_API_URL: this.env.PUBLIC_URL,
1320+ GIT_AUTHOR_NAME: caller.login,
1321+ GIT_AUTHOR_EMAIL: `${caller.login}@users.noreply.diffinery.com`,
1322+ GIT_COMMITTER_NAME: caller.login,
1323+ GIT_COMMITTER_EMAIL: `${caller.login}@users.noreply.diffinery.com`,
1324+ }, secret);
1325+ } catch (e) {
1326+ console.error("sandbox boot failed", e);
1327+ this.sql.exec("UPDATE sandboxes SET status = 'failed' WHERE id = ?", id);
1328+ await stub.shutdown().catch(() => {});
1329+ throw new HttpError(503, "the sandbox container did not start; try again");
1330+ }
13241331 return { id, ref: ref ?? m.defaultBranch };
13251332 }
13261333
modifiedapps/diffinery/src/runner/containers.ts+7−1View file
@@ -50,7 +50,13 @@
5050
5151 async boot(envVars: Record<string, string>, secret: string): Promise<void> {
5252 await this.ctx.storage.put("secret", secret);
53- await this.startAndWaitForPorts({ ports: 8080, startOptions: { envVars, entrypoint: ["node", "/opt/diffinery/agent.mjs", "--sandbox"] } });
53+ // The first start on a host after a deploy pulls the runner image, which takes
54+ // well over the library's 8 s / 20 s defaults.
55+ await this.startAndWaitForPorts({
56+ ports: 8080,
57+ startOptions: { envVars, entrypoint: ["node", "/opt/diffinery/agent.mjs", "--sandbox"] },
58+ cancellationOptions: { instanceGetTimeoutMS: 180_000, portReadyTimeoutMS: 120_000, waitInterval: 500 },
59+ });
5460 }
5561
5662 /** Forward an API call (JSON text) to the sandbox's HTTP server with its secret attached. */