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
- Export GLM_API_KEY as a dot-separated 'id.secret' pair
- Or run: zeroclaw quickstart --model-provider glm --api-key <id.secret>
- Trim whitespace/newlines when loading the key
- 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
- Fail fast at startup: validate the id.secret shape before first use
- Trim env values when loading; newlines break the split
- Add a config lint for GLM keys in CI
- Never reuse OpenAI-style sk- keys with the GLM provider
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
- providers.models.ollama.{alias}.model uses ':cloud', but no
- cloud_ops.iac_tools must not be empty when cloud_ops is enab
- gateway.path_prefix contains invalid character '{bad}'; only
- risk_profiles.{profile_alias}.shell_env_passthrough[{i}] is
- security.otp.cache_valid_secs must be greater than or equal
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/57684fc6a03a6e12.
Report an issue: GitHub.