Hmbown/CodeWhale · error

Cloud agent repository clone failed

Error message

Cloud agent repository clone failed (HTTP {status}).

What it means

Thrown by `clone_repository` when the sandbox toolbox HTTP endpoint `POST git/clone` returns a non-2xx status. The dispatcher already validated the remote URL and sent `{url, path}` to the sandbox; the sandbox itself refused or failed the clone. The provider HTTP status is surfaced in the message so the developer can distinguish auth (401/403), bad request (400/422), and server-side failures (5xx).

Solutions

  1. Read the HTTP status in the message: 401/403 means supply repository credentials the sandbox can use (deploy key/token in the remote URL via validate_git_remote_url-accepted forms); 404 means the repo URL is wrong or private.
  2. Verify the repo URL is reachable and the correct branch/ref exists; test `git ls-remote <url>` locally.
  3. Retry the job if the status is 5xx — the sandbox toolbox may have been temporarily unhealthy.
  4. Ensure the target clone path inside SANDBOX_WORKSPACE is not pre-occupied; a stale sandbox from a prior run may still hold the path — tear it down and retry.
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight: confirm the repo is reachable before dispatching the cloud job
let out = std::process::Command::new("git")
    .args(["ls-remote", repo_url])
    .output()?;
if !out.status.success() {
    anyhow::bail!("repo URL unreachable before cloud dispatch: {}", String::from_utf8_lossy(&out.stderr));
}

Try / catch

match result {
    Err(e) if e.to_string().contains("clone failed (HTTP 4") => show("check repo URL / credentials for the sandbox"),
    Err(e) if e.to_string().contains("clone failed (HTTP 5") => retry_with_backoff(job, 2),
    other => other,
}

Prevention

When it happens

Trigger: Any cloud-agent run that clones a repository into the sandbox when the toolbox `git/clone` call fails: invalid or unauthorized repo URL (private repo without credentials the sandbox can use), nonexistent repo/ref, unwritable target path inside the sandbox, or toolbox returning 5xx.

Common situations: Cloning a private repository the sandbox token cannot read; typo'd or renamed repo URL; the target `path` already exists or is not writable; large monorepo clone timing out at the toolbox; sandbox disk quota exceeded (507/5xx).

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


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

Appendix: source

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

                    if response.status().as_u16() == 404 {
                        bail!("Cloud agent sandbox disappeared before it was ready.");
                    }
                }
            }
            std::thread::sleep(READY_POLL_INTERVAL);
        }
        bail!("Cloud agent sandbox was not ready in time.");
    }

    fn clone_repository(&self, receipt: &SandboxReceipt, repo_url: &str, path: &str) -> Result<()> {
        let repo_url = validate_git_remote_url(repo_url)?;
        let api_key = Self::api_key()?;
        let url = Self::toolbox_base(receipt)?.join("git/clone")?;
        let body = serde_json::json!({ "url": repo_url, "path": path });
        let response = Self::send_json(reqwest::Method::POST, &url, &api_key, body)?;
        let status = response.status();
        if !status.is_success() {
            bail!("Cloud agent repository clone failed (HTTP {status}).");
        }
        Ok(())
    }

    fn run_harness(&self, receipt: &SandboxReceipt, command: &HarnessCommand) -> Result<String> {
        let api_key = Self::api_key()?;
        let url = Self::toolbox_base(receipt)?.join("process/execute")?;
        // The toolbox executes one shell command string, so every argv
        // element is POSIX-single-quoted — a prompt cannot interpolate.
        let body = serde_json::json!({
            "command": shell_quote_join(&command.argv),
            "cwd": command.cwd,
            "timeout": command.timeout_secs,
        });
        // This call carries the declared turn budget (an hour for the agent
        // entry), so it asks for that budget plus slack per request — never the
        // 120s control-plane cap that used to bound it.
        let response = Self::send_json_on(

View on GitHub (pinned to 73e0f67d83)