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

  1. Print the raw JSON the provider produced and compare it against the published ExternalAuthOutput contract (flat object with non-empty access_token).
  2. Fix the provider to emit the exact expected schema (snake_case fields, access_token present and non-empty).
  3. On provider-side errors, exit non-zero or print non-JSON diagnostics to stderr so the shell reports the real failure.
  4. 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

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


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/b3f01bcdeda100cd. Report an issue: GitHub.