BoundaryML/baml · error

{e:?}

Error message

{e:?}

What it means

When exchanging the signed JWT for an OAuth2 access token with Google's token endpoint, any failure from `encode_jwt` (JWT signing/encoding) is re-thrown with its Debug formatting. This surfaces JWT construction failures — most commonly key parsing or serialization errors — without a friendly message.

Source

Thrown at engine/baml-runtime/src/internal/llm_client/primitive/vertex/wasm_auth.rs:149

        }
    }
}

#[derive(Debug, Deserialize)]
pub struct ServiceAccount {
    pub token_uri: String,
    pub project_id: String,
    pub client_email: String,
    pub private_key: String,
}

impl ServiceAccount {
    async fn get_oauth2_token(&self) -> Result<Token> {
        let claims = Claims::from_service_account(self);

        let jwt = encode_jwt(&serde_json::to_value(claims)?, &self.private_key)
            .await
            .map_err(|e| anyhow::anyhow!(format!("{e:?}")))?;

        // Make the token request
        let client = reqwest::Client::new();
        let params = [
            ("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"),
            ("assertion", &jwt),
        ];
        let res = client
            .post(&self.token_uri)
            .form(&params)
            .send()
            .await?
            .text()
            .await?;

        parse_token_response(&res).context(format!("OAuth2 access token request failed: {res}"))
    }
}

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Inspect the inner error text (`e:?` in the message) to identify whether it is key parsing or serialization; read the full Debug string in logs.
  2. Re-download the service account JSON key from GCP Console and load it verbatim (fix escaped `\n` in private_key).
  3. Ensure the private key is a supported format (RSA PEM, PKCS#8) with correct newlines; convert if needed with `openssl`.
  4. Rotate to a fresh, non-deleted service account key.

Example fix

// before: private_key pasted into YAML, newlines literal
private_key: "-----BEGIN PRIVATE KEY-----\n...broken"

// after: load from the JSON key file so real newlines are preserved
let sa: ServiceAccount = serde_json::from_str(&std::fs::read_to_string("sa-key.json")?)?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate the private key parses before token exchange
const key = sa.private_key;
if (!key || !key.includes('-----BEGIN') || !key.includes('\\n') === false && key.split('\n').length < 2) {
  throw new Error('service account private_key malformed');
}

Type guard

function hasValidPrivateKey(sa) { return typeof sa.private_key === 'string' && sa.private_key.startsWith('-----BEGIN') && sa.private_key.includes('END'); }

Try / catch

try { await getOAuth2Token(sa); } catch (e) { console.error('JWT/token exchange failed:', e); throw new Error('Check service-account private_key format and freshness'); }

Prevention

When it happens

Trigger: Calling `ServiceAccount::get_oauth2_token` where `encode_jwt(&claims, &self.private_key)` fails: malformed/unsupported PEM private key, JSON serialization failure of the claims, or an encoding library error.

Common situations: A service-account JSON whose `private_key` contains unescaped newlines (copy-pasted or YAML-mangled), a key not in PKCS#8/RSA format, or an expired/rotated service account key.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/57c13acb7bc5fcc0. Report an issue: GitHub.