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

GLM API key not set or invalid format. Expected 'id.secret'.

Error message

GLM API key not set or invalid format. Expected 'id.secret'. Set GLM_API_KEY env var or run `zeroclaw quickstart --model-provider glm --api-key <id.secret>`.

What it means

GlmModelProvider could not build its JWT because the key is absent or lacks the 'id.secret' shape. GlmModelProvider::new splits the input on the first dot; an empty id or empty secret (including no dot at all) trips this bail, which names both remediation commands explicitly.

Source

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

impl GlmModelProvider {
    pub fn new(api_key: Option<&str>) -> Self {
        let (id, secret) = api_key
            .and_then(|k| k.split_once('.'))
            .map(|(id, secret)| (id.to_string(), secret.to_string()))
            .unwrap_or_default();

        Self {
            api_key_id: id,
            api_key_secret: secret,
            base_url: "https://api.z.ai/api/paas/v4".to_string(),
            token_cache: Mutex::new(None),
        }
    }

    fn generate_token(&self) -> anyhow::Result<String> {
        if self.api_key_id.is_empty() || self.api_key_secret.is_empty() {
            anyhow::bail!(
                "GLM API key not set or invalid format. Expected 'id.secret'. \
                 Set GLM_API_KEY env var or run `zeroclaw quickstart --model-provider glm --api-key <id.secret>`."
            );
        }

        let now_ms = SystemTime::now()
            .duration_since(UNIX_EPOCH)?
            .as_millis() as u64;

        // Check cache (valid for 3 minutes, token expires at 3.5 min)
        if let Ok(cache) = self.token_cache.lock() {
            if let Some((ref token, expiry)) = *cache {
                if now_ms < expiry {
                    return Ok(token.clone());
                }
            }
        }

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Export GLM_API_KEY as a dot-separated 'id.secret' pair
  2. Or run: zeroclaw quickstart --model-provider glm --api-key <id.secret>
  3. Trim whitespace/newlines when loading the key
  4. Confirm on the Z.ai console that the key really has the id.secret form

Example fix

# before
export GLM_API_KEY=sk-abcdef123

# after
export GLM_API_KEY="<key-id>.<key-secret>"  # dot-separated, no spaces
Defensive patterns

Strategy: validation

Validate before calling

fn glm_key_valid(key: &str) -> bool {
    match key.trim().split_once('.') {
        Some((id, secret)) => !id.is_empty() && !secret.is_empty(),
        None => false,
    }
}

// use before constructing the provider:
let key = std::env::var("GLM_API_KEY")?;
anyhow::ensure!(glm_key_valid(&key), "GLM_API_KEY must look like id.secret");

Type guard

fn glm_key_valid(key: &str) -> bool {
    matches!(key.trim().split_once('.'), Some((id, s)) if !id.is_empty() && !s.is_empty())
}

Try / catch

if let Err(e) = provider.chat_with_system(None, prompt, model, temp).await {
    if e.to_string().starts_with("GLM API key not set") {
        return Err(anyhow::anyhow!("configure GLM_API_KEY before starting GLM jobs"));
    }
    return Err(e);
}

Prevention

When it happens

Trigger: GLM_API_KEY unset; key with no dot; key with an empty half such as '.secret' or 'id.'; passing None to the constructor; key with surrounding whitespace so one side trims empty.

Common situations: Env var forgotten in deployment; .env file not loaded; trailing newline in the secret; using an OpenAI-style sk-... key with the GLM provider by mistake.

Understand the failure class

Background: "API key is required" / "API key not found" / "No API key was set": the missing-api-key error family across 16 libraries — this error's family across 16 libraries.

Related errors


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