xai-org/grok-build · error
produced JSON that is not a token payload: {e}
Error message
produced JSON that is not a token payload: {e} What it means
When the provider's stdout starts with '{', parse_token_output treats it strictly as a token payload and deserializes it into ExternalAuthOutput, requiring an access_token. If serde_json cannot deserialize the text into that struct, this error is returned (wrapped by the caller with the command name). This prevents error objects like {"error":"expired"} from being mistaken for bearer tokens.
Source
Thrown at crates/codegen/xai-grok-shell/src/auth/token_output.rs:66
output: &std::process::Output,
) -> anyhow::Result<ParsedTokenOutput> {
if !output.status.success() {
anyhow::bail!("exited with {}", output.status);
}
let stdout = std::str::from_utf8(&output.stdout)
.map_err(|_| anyhow::anyhow!("produced non-UTF-8 output on stdout"))?
.trim();
if stdout.is_empty() {
anyhow::bail!("produced no output on stdout");
}
// Output that starts with `{` is meant to be a token payload: require it to
// parse and carry a non-empty access_token. Anything else is a bare token
// (JWTs and opaque tokens never start with `{`), so an error object like
// `{"error":"expired"}` can never be mistaken for a bearer.
if stdout.starts_with('{') {
let parsed: ExternalAuthOutput = serde_json::from_str(stdout)
.map_err(|e| anyhow::anyhow!("produced JSON that is not a token payload: {e}"))?;
let access_token = parsed.access_token.trim().to_owned();
if access_token.is_empty() {
anyhow::bail!("produced JSON with an empty access_token");
}
reject_control_chars(&access_token)?;
tracing::debug!(
has_refresh_token = parsed.refresh_token.is_some(),
expires_in = ?parsed.expires_in,
issuer = ?parsed.issuer,
"auth: parsed external provider output as JSON"
);
return Ok(ParsedTokenOutput {
access_token,
refresh_token: parsed.refresh_token,
expires_at: parsed.expires_in.and_then(expiry_after_seconds),
issuer: parsed
.issuer
.map(|i| i.trim().to_owned())View on GitHub (pinned to bc7f02eddd)
Solutions
- Print the raw JSON the provider produced and compare it against the published ExternalAuthOutput contract (flat object with non-empty access_token).
- Fix the provider to emit the exact expected schema (snake_case fields, access_token present and non-empty).
- On provider-side errors, exit non-zero or print non-JSON diagnostics to stderr so the shell reports the real failure.
- Update the shell or provider so both sides agree on the contract version.
Example fix
// before
{"result":{"access_token":"eyJ..."}}
// after
{"access_token":"eyJ...","refresh_token":"..."} Defensive patterns
Strategy: type-guard
Validate before calling
const out = execSync(provider_cmd, { encoding: 'utf8' }).trim();
if (out.startsWith('{')) {
const j = JSON.parse(out);
if (!('access_token' in j) || !String(j.access_token).trim()) {
throw new Error('provider JSON missing non-empty access_token');
}
} Type guard
function isExternalAuthOutput(v) {
return typeof v === 'object' && v !== null && !Array.isArray(v) &&
typeof v.access_token === 'string' && v.access_token.trim().length > 0;
} Try / catch
match run_auth_flow(...).await {
Err(e) if e.to_string().contains("produced JSON that is not a token payload") => {
eprintln!("Provider returned malformed/error JSON — diff its stdout against the ExternalAuthOutput contract");
}
other => other?,
} Prevention
- Validate provider JSON against the contract in the provider's own tests
- Use a flat snake_case schema with a non-empty access_token
- Report provider errors via exit code + stderr, never as stdout JSON
- Bump and test both sides together when the schema changes
When it happens
Trigger: The provider prints JSON on stdout that doesn't match ExternalAuthOutput's schema: missing access_token, wrong field types (e.g. numeric expires_in where string expected), extra incompatible shapes, or nested error objects.
Common situations: Provider returns {"error":...} on failure; provider upgraded and changed JSON field names; snake_case vs camelCase mismatch; provider emits an envelope like {"data":{"access_token":...}} instead of a flat object.
Related errors
- auth entry '{scope_key}' not found while persisting refreshe
- failed to start auth provider `{command}`: {e}
- external auth provider `{command}` timed out after 300s
- external auth provider `{command}` IO error: {e}
- external auth provider `{command}`: {e}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/b3f01bcdeda100cd.
Report an issue: GitHub.