Hmbown/CodeWhale · error · anyhow::Error

cloud dispatch fails closed: the cloud agent runs Codewhale…

Error message

cloud dispatch fails closed: the cloud agent runs Codewhale itself, so it needs a Codewhale account machine token to act as your account. Set CODEWHALE_API_KEY to a `cwc_key_…` machine key (Account → API keys in the web app) and confirm again.

What it means

create_sandbox requires a Codewhale account machine token to mint a cloud sandbox that acts as your account; read_cloud_agent_token() returned None, so dispatch fails closed. The message explains the token is distinct from the API key and where to obtain it.

Solutions

  1. Set/provision a `cwc_key_…` machine token as instructed (Account → API keys) and confirm again
  2. Re-run the cloud dispatch confirmation flow to store the token
  3. Check that the token storage location is readable and not cleared between runs
  4. Regenerate the machine key if it was revoked

Example fix

// before
read_cloud_agent_token() // None
// after
export CODEWHALE_API_KEY=cwc_key_xxx && re-run dispatch confirm to persist the machine token
Defensive patterns

Strategy: validation

Validate before calling

let has_token = read_cloud_agent_token().is_some();
let has_key = std::env::var("CODEWHALE_API_KEY").is_ok();

Try / catch

match launcher.create_sandbox(&job) {
    Err(e) if e.to_string().contains("machine token") => {
        run_confirm_flow_to_provision_token()?;
        retry();
    }
    other => other?,
}

Prevention

When it happens

Trigger: LiveDaytonaLauncher::create_sandbox reaches `read_cloud_agent_token().ok_or_else(...)` after the API key check passes, but no cloud-agent machine token is stored (crates/tui/src/cloud_dispatch.rs:1493).

Common situations: User set CODEWHALE_API_KEY but never completed the machine-token provisioning/confirm step, token expired/revoked and removed from storage, or config synced from a machine without the token.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/268b19d3c58e1bf0. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/cloud_dispatch.rs:1493

        api_key: &str,
        body: serde_json::Value,
    ) -> Result<reqwest::blocking::Response> {
        client
            .request(method, url.clone())
            .timeout(std::time::Duration::from_secs(total_secs))
            .bearer_auth(api_key)
            .json(&body)
            .send()
            .context("could not reach the cloud agent service")
    }
}

impl DaytonaLauncher for LiveDaytonaLauncher {
    fn create_sandbox(&self, job: &CloudJob) -> Result<SandboxReceipt> {
        let api_key = Self::api_key()?;
        let url = Self::control_plane_url("sandbox")?;
        let machine_token =
            read_cloud_agent_token().ok_or_else(|| anyhow!(missing_machine_token_message()))?;
        let body = create_sandbox_body(job, &machine_token, &cloud_agent_snapshot());
        let response = Self::send_json(reqwest::Method::POST, &url, &api_key, body)?;
        let status = response.status();
        let text = response.text().unwrap_or_default();
        if !status.is_success() {
            bail!("Cloud agent create failed (HTTP {status}).");
        }
        let parsed: serde_json::Value =
            serde_json::from_str(&text).context("the cloud agent service returned invalid JSON")?;
        let sandbox_id = parsed
            .get("id")
            .or_else(|| parsed.get("sandboxId"))
            .and_then(serde_json::Value::as_str)
            .unwrap_or("")
            .trim()
            .to_string();
        if !valid_sandbox_id(&sandbox_id) {
            // The provider says the sandbox exists (2xx) but gave us an id

View on GitHub (pinned to 73e0f67d83)