Hmbown/CodeWhale · error · anyhow::Error

The Codewhale service returned an account without an ID

Error message

The Codewhale service returned an account without an ID

What it means

After a successful GET /api/me (HTTP 200, JSON parsed into MeResponse), the CLI validates that user.id is a non-empty trimmed string. An account record without an ID cannot be stored or addressed by later calls, so the response is rejected even though the HTTP call succeeded.

Source

Thrown at crates/cli/src/cloud.rs:382

    }

    fn save_auth(&self, bundle: AuthBundle) -> Result<()> {
        self.account_store
            .save(bundle)
            .context("failed to save the Codewhale account session in the local secret store")
    }

    fn clear_auth(&self) -> Result<()> {
        self.account_store
            .clear()
            .context("failed to remove the local Codewhale account session")
    }

    fn me(&self) -> Result<CloudUser> {
        let response = self.execute_authenticated(HttpMethod::Get, "/api/me", None)?;
        let me: MeResponse = expect_json(response, &[200])?;
        if me.user.id.trim().is_empty() {
            bail!("The Codewhale service returned an account without an ID");
        }
        if let Some(mut stored) = self.load_auth()? {
            stored.bundle.user = Some(me.user.clone());
            self.save_auth(stored.bundle)?;
        }
        Ok(me.user)
    }

    fn set_key(&self, provider: CloudProvider, key: &str, label: &str) -> Result<()> {
        let path = format!("/api/model-keys/{}", provider.slug());
        let response = self.execute_authenticated(
            HttpMethod::Put,
            &path,
            Some(json_body(&ModelKeyRequest { key, label })?),
        )?;
        expect_empty(response, &[200, 201])
    }

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Inspect the raw /api/me response (curl with the stored bearer token) and confirm the user object carries a non-empty id.
  2. If you run a mock server, make it return a realistic id in the user object.
  3. If the real service is returning id-less users, report it as a server-side data/regression issue with the response payload.
  4. Update to matching CLI/service versions in case the field name changed.

Example fix

# mock server before
{"user": {"email": "a@b.c"}}

# mock server after
{"user": {"id": "usr_123", "email": "a@b.c"}}
Defensive patterns

Strategy: validation

Validate before calling

// For mock/stub servers: guarantee the me payload shape
fn valid_me_payload(user: &serde_json::Value) -> bool {
    user.get("id").and_then(|v| v.as_str()).map(|s| !s.trim().is_empty()).unwrap_or(false)
}

Type guard

fn is_valid_cloud_user(v: &serde_json::Value) -> bool {
    v.pointer("/user/id")
        .and_then(|id| id.as_str())
        .map(|s| !s.trim().is_empty())
        .unwrap_or(false)
}

Try / catch

match client.me().await {
    Ok(user) => user,
    Err(e) if e.to_string().contains("without an ID") => {
        // 200-but-malformed: do not retry; capture raw response and report upstream
        report_contract_violation(raw_response).await;
        return Err(e);
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: The service returns 200 with a user object whose id field is missing (serde default), empty, or whitespace-only — e.g. malformed backend record, API contract change, or a mock/test server returning {"user": {}}.

Common situations: Developing against a stub /api/me that omits id, service regression that serializes anonymous/null accounts, or a version skew where the field was renamed and serde silently defaults it.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/5a226330e61498e4. Report an issue: GitHub.