xai-org/grok-build · error · acp::Error
Failed to parse billing data: {e}
Error message
Failed to parse billing data: {e} What it means
The billing endpoint returned HTTP 200, but the body could not be deserialized into `BillingConfigResponse`. The handler logs the parse failure via unified telemetry and returns an ACP internal error 'Failed to parse billing data: {e}'. This is a contract mismatch between what the upstream service sent and the schema the shell expects.
Source
Thrown at crates/codegen/xai-grok-shell/src/extensions/billing.rs:270
Some(serde_json::json!({
"status": status,
"detail": detail,
})),
);
return Err(acp::Error::internal_error().data(format!("Billing service error: {detail}")));
}
let mut billing: BillingConfigResponse = credits_resp.json().await.map_err(|e| {
tracing::error!(error = %e, "billing: failed to parse response");
xai_grok_telemetry::unified_log::warn(
"billing: failed to parse response",
None,
Some(serde_json::json!({ "error": e.to_string() })),
);
acp::Error::internal_error().data(format!("Failed to parse billing data: {e}"))
})?;
// Enrich with fields from remote settings.
let rs = agent.cfg.borrow().remote_settings.clone();
billing.on_demand_enabled = rs.as_ref().and_then(|rs| rs.on_demand_enabled);
billing.subscription_tier = rs.as_ref().and_then(|rs| {
rs.subscription_tier_display
.clone()
.or_else(|| rs.subscription_tier.clone())
});
// Every prompt / /usage / poll path hits `x.ai/billing`; log the fetched
// credits snapshot so support can correlate limit UX with real balances.
xai_grok_telemetry::unified_log::info(
"billing: fetched credits config",
None,
Some(billing_unified_log_ctx(&billing)),
);
to_raw_response(&billing)View on GitHub (pinned to bc7f02eddd)
Solutions
- Read `{e}` (also in telemetry data) to identify which field failed to deserialize.
- Capture the raw response body (curl the credits endpoint) and compare it against the `BillingConfigResponse` schema.
- Upgrade or downgrade the shell so its `BillingConfigResponse` matches the deployed billing API version.
- Check for proxies/captive portals returning non-JSON bodies with 200 status.
Example fix
// before: API changed field type, shell expects bool
{ "on_demand_enabled": "true" }
// after: upstream must send the expected type
{ "on_demand_enabled": true } Defensive patterns
Strategy: type-guard
Validate before calling
// probe the endpoint and check the body is JSON before relying on the parsed result
const body = await fetch(creditsUrl, { headers: auth(token) }).then(r => r.text());
const isJson = body.trimStart().startsWith("{") || body.trimStart().startsWith("[");
if (!isJson) throw new Error("billing endpoint returned non-JSON body"); Type guard
fn is_billing_config(v: &serde_json::Value) -> bool {
serde_json::from_value::<BillingConfigResponse>(v.clone()).is_ok()
} Try / catch
try {
const billing = await acp.request("billing_get");
} catch (e) {
if (String(e.message).startsWith("Failed to parse billing data")) {
dumpRawResponseForDiagnostics(); // compare body to BillingConfigResponse schema
} else { throw e; }
} Prevention
- Keep shell and billing API versions aligned; generate types from the API schema
- Guard against proxies returning HTML error pages with 200 status
- Snapshot-test BillingConfigResponse against live API responses
- Log the raw body when deserialization fails
When it happens
Trigger: `credits_resp.json::<BillingConfigResponse>()` returns `Err(e)` — response body is HTML/JSON from a proxy error page, fields have unexpected types (string vs number), required fields are missing after an API change, or an interceptor returned an empty body.
Common situations: API version drift between shell and billing service (schema change); captive portal or proxy injecting HTML; partial/empty 200 responses during upstream incidents; response envelope renamed (e.g. `on_demand_enabled` type changed).
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Client returned an invalid response to user question: {msg}
- response parse error: {e}
- <serde_json deserialization error for summary.json>
- Failed to create agent config: {e}
- --permission-mode: invalid value: {e}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/0e87473d4e2d0f7e.
Report an issue: GitHub.