Give sandbox task creation a realistic timeout and diagnosable failures - #332454
Merged
Osvaldo Ortega (osortega) merged 3 commits intoAug 25, 2026
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds reliable cloud sandbox task creation and failure diagnostics.
Changes:
- Extends creation timeout to 60 seconds and sends JSON content type.
- Logs failure details and attempts orphan cleanup.
- Adds failure and cleanup tests.
Show a summary per file
| File | Description |
|---|---|
cloudSandboxApiService.ts |
Updates request handling, diagnostics, and cleanup. |
cloudSandboxApiService.test.ts |
Adds failed-create tests and log assertions. |
Review details
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Balanced
Creating a cloud sandbox task ran on the generic 10s request budget, but Mission Control provisions a sandbox VM before it replies, so the call waits on a machine being allocated rather than on a record being written. Every create was aborted client-side before any response arrived, which surfaced as a network failure. Task creation now gets its own 60s budget. Failures were also hard to act on. Mission Control writes the task record before provisioning compute, so a failure can leave a task behind, and the provisioner answers with a generic `failed to create agent compute` that masks the upstream cause. Failed creates now log the response verbatim, including `x-github-request-id`, which is the only handle that correlates with the owning team's logs. When the response names the task it recorded, that orphan is deleted; when it does not, the log says so explicitly. The response body is read once and threaded through to the thrown error: reading it twice silently yields an empty string and drops the message explaining the failure. Also fixes a hardcoded `GET` in the request failure log, and sends `Content-Type: application/json` for bodied requests, which `fetch` would otherwise label `text/plain`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A rejected DELETE resolves like any other response rather than throwing, so the cleanup logged "Cleaned up unusable sandbox task" for an HTTP 500 and the orphan it claimed to remove was still there. The status now decides, and a failed cleanup takes the same warning path as a thrown one, saying explicitly that the task remains and can only be removed server-side. The existing cleanup test reused the create's 500 for the DELETE too, so it passed without ever showing a successful removal. The stub now answers the delete on its own, and a new test covers the rejected case directly. Assert the creation budget and the JSON content type, which no test covered: the request stub discarded `timeout` and `headers`, so reverting either production change left the suite green. The budget is pinned as a literal rather than compared against its own constant, which would still pass if it were lowered back to the value that aborted every create. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Osvaldo Ortega (osortega)
force-pushed
the
osortega/sandbox-task-create-reliability
branch
from
August 25, 2026 00:59
a04e77f to
93387ec
Compare
Osvaldo Ortega (osortega)
marked this pull request as ready for review
August 25, 2026 02:52
Osvaldo Ortega (osortega)
enabled auto-merge (squash)
August 25, 2026 02:52
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
joshspicer
approved these changes
Aug 25, 2026
Osvaldo Ortega (osortega)
deleted the
osortega/sandbox-task-create-reliability
branch
August 25, 2026 03:25
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Creating a cloud sandbox task ran on the generic 10s request budget, but the call waits on a sandbox environment being provisioned rather than on a record being written. Successful creates routinely take considerably longer than that, so every create was aborted client-side before any response arrived. It surfaced as a network failure, when really we were hanging up while the request was still in flight.
What this does
Task creation gets its own 60s budget. Other calls keep the 10s one.
Failures are now diagnosable. A failed create can come back with a generic message that does not identify the underlying cause, so nothing client-side can explain it on its own. Failed creates now log the response verbatim — status, body, and
x-github-request-id, which is what a support escalation is keyed on. A small set of retry-related response headers is captured for the same reason.Orphaned tasks are cleaned up. A task record can already exist by the time a create fails, so a failure can leave one behind — not just the authorization rejection that was previously handled. When the response names the task, it is deleted; when it does not, the log says so explicitly rather than staying silent about something that can then only be removed server-side.
A rejected cleanup is no longer reported as a successful one.
_requestresolves non-2xx responses rather than throwing, so a failed DELETE was logged as "Cleaned up" while the orphan was still there. The status now decides, and a rejected delete takes the same warning path as a thrown one.Two subtleties worth a reviewer's eye
asText(context)consumes the stream. Reading it twice silently yields an empty string, which was dropping the message explaining the failure — so the body is read once and threaded through to_throwForStatus.Also fixes a hardcoded
GETin the request failure log, and sendsContent-Type: application/jsonfor bodied requests, whichfetchwould otherwise labeltext/plain.Validation
npm run typecheck-client,eslinton both changed files./scripts/test.sh --glob "**/cloudSandboxApiService.test.js"— 17 passingEach new assertion was checked against the bug it is meant to catch, by reverting the production change and confirming the test fails:
posts the on-demand sentinel and returns the bound environmentContent-Type: application/jsonisSuccesscheck on cleanupreports a rejected cleanup rather than claiming the orphan was removedThe request stub previously discarded
timeoutandheaders, so an earlier revision of this PR would have stayed green with the budget and content type reverted. It no longer does.