Hmbown/CodeWhale · error · anyhow::Error

Cloud agents are not available for this account yet; cloud…

Error message

Cloud agents are not available for this account yet; cloud dispatch fails closed (no sandbox, no push, no PR). | Cloud agents are included with your Codewhale membership. Sign in with `codewhale login` to enable `/dispatch`; cloud dispatch fails closed until then (no sandbox, no push, no PR).

What it means

Cloud dispatch refuses to run because Codewhale account credentials are missing: `api_key()` calls `read_api_key()` and, when absent, fails with `missing_credentials_message()`. That message states cloud agents are included with a Codewhale membership and that dispatch fails closed — no sandbox, no push, no PR — until the user signs in with `codewhale login`. The failure is deliberate: cloud dispatch must never run unauthenticated.

Solutions

  1. Run `codewhale login` to authenticate and store the API key
  2. Verify the credential file exists in the Codewhale home (`CODEWHALE_HOME` / user home)
  3. Set a valid API key if the credential reader supports environment fallback
  4. Re-run `/dispatch` after authenticating

Example fix

# before
$ codewhale  # then /dispatch -> fails closed
# after
$ codewhale login
$ codewhale  # then /dispatch -> proceeds
Defensive patterns

Strategy: validation

Validate before calling

# check credentials before dispatch
if not codewhale_logged_in():
    raise SystemExit("Run 'codewhale login' before /dispatch")

Type guard

fn can_dispatch() -> bool { read_api_key().is_some() }

Try / catch

if let Err(e) = dispatch(job).await {
    if e.to_string().contains("fails closed") {
        eprintln!("Not signed in. Run: codewhale login");
        return;
    }
    return Err(e);
}

Prevention

When it happens

Trigger: Invoking `/dispatch` (or `execute_dispatch`/`confirm_job`) without a stored API key: never having run `codewhale login`, credentials deleted/expired from local storage, or running in an environment without the saved auth state.

Common situations: Fresh machine install before login; CI or container environments lacking the credential store; corrupted or cleared config directory; expired stored session removed by the credential reader.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/799004dc878ad5b7. Report an issue: GitHub.

Appendix: source

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

    /// A client scoped to one harness command: its total timeout is the
    /// command's declared budget plus fixed slack. The declared turn budget
    /// is an hour, so the 120s control-plane cap must NOT carry this call —
    /// otherwise every dispatched turn longer than two minutes fails after
    /// the spend has already started.
    fn harness_client(command: &HarnessCommand) -> Result<reqwest::blocking::Client> {
        Self::blocking_client_with_timeout(Self::harness_client_budget_secs(command))
    }

    /// The total-timeout budget for a harness-carrying client, in seconds.
    /// Public to the crate so the runner's tests can pin it against the
    /// declared `HARNESS_TIMEOUT_SECS`.
    pub(crate) fn harness_client_budget_secs(command: &HarnessCommand) -> u64 {
        u64::from(command.timeout_secs).saturating_add(Self::HARNESS_CLIENT_SLACK_SECS)
    }

    fn api_key() -> Result<String> {
        read_api_key().ok_or_else(|| anyhow!(missing_credentials_message()))
    }

    /// Control-plane URL under the validated base.
    fn control_plane_url(path: &str) -> Result<reqwest::Url> {
        let base = validate_outbound_origin(&daytona_api_url())?;
        join_api_path(base, path).context("failed to build the cloud agent request URL")
    }

    /// Toolbox base for one sandbox: `{toolboxProxyUrl}/{sandboxId}`.
    fn toolbox_base(receipt: &SandboxReceipt) -> Result<reqwest::Url> {
        if !valid_sandbox_id(&receipt.sandbox_id) {
            bail!("the sandbox id is not a usable path token");
        }
        let fallback = format!("{}/toolbox", DEFAULT_DAYTONA_API);
        let raw = receipt.toolbox_url.as_deref().unwrap_or(&fallback);
        let base = validate_outbound_origin(raw)?;
        join_api_path(base, &receipt.sandbox_id).context("failed to build the sandbox toolbox URL")
    }

View on GitHub (pinned to 433685b202)