zeroclaw-labs/zeroclaw · critical · anyhow::Error
tenant_access_token failed: {msg}
Error message
tenant_access_token failed: {msg} What it means
The tenant_access_token endpoint returned HTTP 2xx, but the JSON body carried a non-zero Lark business code. Lark reports application errors as {"code": ..., "msg": ...} even with a 200 status; this variant almost always means the credentials themselves are wrong or the app is gone. The msg from the body is included ("unknown error" if the field is missing), and code=-1 appears when no code field could be parsed.
Source
Thrown at crates/zeroclaw-channels/src/lark.rs:1506
"app_id": self.app_id,
"app_secret": self.app_secret,
});
let resp = self.http_client().post(&url).json(&body).send().await?;
let status = resp.status();
let data: serde_json::Value = resp.json().await?;
if !status.is_success() {
anyhow::bail!("tenant_access_token request failed: status={status}, body={data}");
}
let code = data.get("code").and_then(|c| c.as_i64()).unwrap_or(-1);
if code != 0 {
let msg = data
.get("msg")
.and_then(|m| m.as_str())
.unwrap_or("unknown error");
anyhow::bail!("tenant_access_token failed: {msg}");
}
let token = data
.get("tenant_access_token")
.and_then(|t| t.as_str())
.ok_or_else(|| {
::zeroclaw_log::record!(
WARN,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
.with_outcome(::zeroclaw_log::EventOutcome::Failure),
"missing tenant_access_token in response"
);
anyhow::Error::msg("missing tenant_access_token in response")
})?
.to_string();
let ttl_seconds = extract_lark_token_ttl_seconds(&data);
let refresh_after = next_token_refresh_deadline(Instant::now(), ttl_seconds);View on GitHub (pinned to 88bb9c8533)
Solutions
- Re-copy both App ID and App Secret from the Lark developer console (Credentials page) and update [channels.lark.<alias>] exactly.
- Inspect the secret for stray whitespace, quotes, or newlines and strip them.
- Confirm the app still exists, is enabled, and has not been deleted.
- After fixing credentials, restart the channel so it drops any cached token state.
Example fix
# before (TOML config) [channels.lark.main] app_id = "cli_a1b2c3" app_secret = "\"xYz...secret...\"\n" # pasted with quotes and trailing newline # after [channels.lark.main] app_id = "cli_a1b2c3" app_secret = "xYz...secret..."
Defensive patterns
Strategy: validation
Validate before calling
// Fail fast at startup on bad credentials instead of mid-conversation
let token = lark.get_tenant_access_token().await
.context("Lark credentials rejected at startup; check app_id/app_secret in [channels.lark.<alias>]")?; Try / catch
if let Err(e) = lark.get_tenant_access_token().await {
if e.to_string().contains("tenant_access_token failed") {
// business-code rejection: do NOT retry; credentials are wrong
return Err(e.context("fix Lark app credentials before retrying"));
}
return Err(e);
} Prevention
- Run a startup health check that fetches one tenant_access_token before accepting traffic.
- Store secrets via a secret manager; never hand-edit them in config files where whitespace sneaks in.
- When rotating the Lark app secret, update config and restart in the same change.
When it happens
Trigger: Invalid or revoked app_id/app_secret, an app that was deleted or disabled in the Lark developer console, a secret with trailing whitespace/newline/quotes copied from the console, or credentials from a different app pasted into config.
Common situations: The app secret was rotated in the Lark console but the config was never updated; the App ID was pasted into the app_secret field; environment-specific credentials (dev vs prod app) got mixed; secret stored with surrounding quotes or a newline.
Related errors
- tenant_access_token request failed: status={status}, body={d
- createSession failed ({status}): {body}
- send failed {context}: status={status}, body={body}
- send failed {context}: code={code}, body={body}
- WS endpoint failed: code={} msg={}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/c40cbff95de59abe.
Report an issue: GitHub.