zeroclaw-labs/zeroclaw · error · anyhow::Error

GLM API error: {error}

Error message

GLM API error: {error}

What it means

chat_with_system POSTed to {base_url}/chat/completions and got a non-2xx; the raw response body is embedded verbatim after 'GLM API error:'. The body distinguishes bad JWT/key (401), unknown model, rate limit (429), and context overflow.

Source

Thrown at crates/zeroclaw-providers/src/glm.rs:196

        let request = ChatRequest {
            model: model.to_string(),
            messages,
            temperature,
        };

        let url = format!("{}/chat/completions", self.base_url);

        let response = self
            .http_client()
            .post(&url)
            .header("Authorization", format!("Bearer {token}"))
            .json(&request)
            .send()
            .await?;

        if !response.status().is_success() {
            let error = response.text().await?;
            anyhow::bail!("GLM API error: {error}");
        }

        let chat_response: ChatResponse = response.json().await?;

        chat_response
            .choices
            .into_iter()
            .next()
            .map(|c| c.message.content)
            .ok_or_else(|| {
                ::zeroclaw_log::record!(
                    ERROR,
                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
                        .with_outcome(::zeroclaw_log::EventOutcome::Failure),
                    "glm: empty choices in response"
                );
                anyhow::Error::msg("No response from GLM")
            })

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Read the embedded body: 401 -> verify the id.secret key; model-not-found -> use a current model id; 429 -> back off
  2. Verify the system clock (JWT exp depends on it)
  3. Restore the default base_url https://api.z.ai/api/paas/v4 unless using a compatible gateway
  4. Retry with backoff only for 429/5xx-class bodies

Example fix

// before
let text = provider.chat_with_system(None, prompt, model, temp).await?;

// after
let text = match provider.chat_with_system(None, prompt, model, temp).await {
    Ok(t) => t,
    Err(e) if e.to_string().contains("429") => {
        tokio::time::sleep(std::time::Duration::from_secs(10)).await;
        provider.chat_with_system(None, prompt, model, temp).await?
    }
    Err(e) => return Err(e),
};
Defensive patterns

Strategy: try-catch

Validate before calling

fn glm_credentials_ready() -> bool {
    std::env::var("GLM_API_KEY").map(|k| glm_key_valid(&k)).unwrap_or(false)
}

Type guard

fn is_glm_http_error(e: &anyhow::Error) -> bool {
    e.to_string().starts_with("GLM API error: ")
}

Try / catch

match provider.chat_with_system(None, prompt, model, temp).await {
    Ok(t) => Ok(t),
    Err(e) => {
        let msg = e.to_string();
        if msg.contains("429") || msg.contains("500") {
            backoff_and_retry().await
        } else {
            Err(anyhow::anyhow!("GLM call failed: {msg}")) // auth/model: fix config
        }
    }
}

Prevention

When it happens

Trigger: Calling chat_with_system with an unsupported model id for the GLM endpoint; a mis-signed or clock-skewed JWT (tokens expire after 3.5 minutes); 429 rate limiting; a base_url override pointing at an incompatible proxy.

Common situations: Upstream model rename (glm-4 era ids retired); host clock skew breaking the exp claim; corporate proxy rewriting responses; quota exhausted on the Z.ai plan.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/8c888483e090e16e. Report an issue: GitHub.