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 @@ | ||
| 283 | 283 | run: | |
| 284 | 284 | set -euo pipefail |
| 285 | 285 | 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) | |
| 287 | 287 | echo "sandbox $id" |
| 288 | 288 | for i in $(seq 1 40); do |
| 289 | 289 | 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 @@ | ||
| 1310 | 1310 | const secret = randomToken(32); |
| 1311 | 1311 | 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()); |
| 1312 | 1312 | 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 | + } | |
| 1324 | 1331 | return { id, ref: ref ?? m.defaultBranch }; |
| 1325 | 1332 | } |
| 1326 | 1333 |
modifiedapps/diffinery/src/runner/containers.ts+7−1View file
| @@ -50,7 +50,13 @@ | ||
| 50 | 50 | |
| 51 | 51 | async boot(envVars: Record<string, string>, secret: string): Promise<void> { |
| 52 | 52 | 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 | + }); | |
| 54 | 60 | } |
| 55 | 61 | |
| 56 | 62 | /** Forward an API call (JSON text) to the sandbox's HTTP server with its secret attached. */ |