Hmbown/CodeWhale · error
Cloud agent create succeeded but returned no usable sandbox…
Error message
Cloud agent create succeeded but returned no usable sandbox id (raw: "{raw}"). A sandbox may need manual cleanup at the provider. What it means
The create call returned HTTP success but the JSON body contained no id field usable as a path token (`id`/`sandboxId` missing or failing `valid_sandbox_id`). The library attempts a best-effort DELETE of the raw id and then fails, warning that an orphaned sandbox may need manual cleanup at the provider.
Solutions
- Inspect the raw body shown in the error message to identify the actual response shape
- Check for orphaned sandboxes in the Daytona dashboard and delete them manually
- Update the id-extraction code (parsed.get("id").or(get("sandboxId"))) if the provider renamed the field
- Pin/verify the Daytona API version the client targets
Example fix
// before
let sandbox_id = parsed.get("id").or_else(|| parsed.get("sandboxId"));
// after
// after updating extraction for the provider's new field name:
let sandbox_id = parsed.get("id").or_else(|| parsed.get("sandboxId")).or_else(|| parsed.get("sandbox_id")); Defensive patterns
Strategy: fallback
Validate before calling
// after parsing, assert the id is usable before proceeding
let id = parsed.get("id").and_then(|v| v.as_str()).unwrap_or("");
if !valid_sandbox_id(id) { warn!("unexpected create response: {parsed}"); } Type guard
fn extract_sandbox_id(v: &serde_json::Value) -> Option<&str> {
["id", "sandboxId", "sandbox_id"].iter().find_map(|k| v.get(k)?.as_str()).filter(|s| valid_sandbox_id(s))
} Try / catch
let id = extract_sandbox_id(&parsed).ok_or_else(|| anyhow!("create returned no usable id"))?; Prevention
- Pin and test against the Daytona API version in CI with a contract test
- Alert on 2xx responses whose body lacks the expected id field
- Keep the accepted id-field list in one place and update it with provider changes
When it happens
Trigger: Daytona returns 2xx with an unexpected body: an error envelope with 200, a changed field name, an empty id, or an id containing slashes/invalid characters.
Common situations: Daytona API version drift (response schema changed), an auth proxy swallowing the real response, or a partially failed create that returned 200 with a stub body.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- cloud agent was created but its labels could not be applied…
- Cloud agent create failed
- cloud agent created but its labels could not be applied
- cloud agent label apply failed
- Cloud agent sandbox disappeared before it was ready.
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/fbc605056170bac4.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/cloud_dispatch.rs:1531
// is impossible. Best-effort delete with the raw string when it
// is at least non-empty (the DELETE path itself validates and
// will refuse dangerous shapes), and always name it in the
// error so an operator can clean it up.
let raw = parsed
.get("id")
.or_else(|| parsed.get("sandboxId"))
.and_then(serde_json::Value::as_str)
.unwrap_or("")
.trim();
if !raw.is_empty() && valid_sandbox_id(raw) {
let _ = Self::send_json(
reqwest::Method::DELETE,
&Self::control_plane_url(&format!("sandbox/{raw}"))?,
&api_key,
serde_json::Value::Null,
);
}
bail!(
"Cloud agent create succeeded but returned no usable sandbox id (raw: \"{raw}\"). \
A sandbox may need manual cleanup at the provider."
);
}
let toolbox_url = parsed
.get("toolboxProxyUrl")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty() && value.len() <= MAX_REMOTE_BYTES)
.and_then(|value| validate_outbound_origin(value).ok())
.map(|url| url.to_string());
// Daytona applies labels via a dedicated PUT, not the create body
// (kept there for forward compatibility). Labels are load-bearing:
// the orphan reconciler joins them back to job records, so a
// sandbox without them is untraceable spend. If the PUT fails, the
// already-created sandbox is torn down immediately and create
// fails truthfully — no orphan, retryable — rather than returning
// a receipt the reconciler can never find again.View on GitHub (pinned to 73e0f67d83)