googleworkspace/cli · error · GwsError
Failed to parse message: {e}
Error message
Failed to parse message: {e} What it means
After a successful HTTP 2xx from `GET /gmail/v1/users/me/messages/{id}?format=full`, `resp.json::<Value>()` failed — the body was not valid JSON. This means Gmail (or an intercepting proxy) returned a 200 with a non-JSON payload, e.g. an HTML error page, an empty body, or a truncated response.
Source
Thrown at crates/google-workspace-cli/src/helpers/gmail/mod.rs:402
.map_err(|e| GwsError::Other(anyhow::anyhow!("Failed to fetch message: {e}")))?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp
.text()
.await
.unwrap_or_else(|_| "(error body unreadable)".to_string());
return Err(build_api_error(
status,
&body,
&format!("Failed to fetch message {message_id}"),
));
}
let msg: Value = resp
.json()
.await
.map_err(|e| GwsError::Other(anyhow::anyhow!("Failed to parse message: {e}")))?;
parse_original_message(&msg)
}
/// Build a `GwsError::Api` from an HTTP error response body, parsing the
/// Google JSON error format when possible. Modeled after the executor's
/// `handle_error_response`, extracting message, reason, and enable URL.
pub(super) fn build_api_error(status: u16, body: &str, context: &str) -> GwsError {
let err_json: Option<Value> = serde_json::from_str(body).ok();
let err_obj = err_json.as_ref().and_then(|v| v.get("error"));
let message = err_obj
.and_then(|e| e.get("message"))
.and_then(|m| m.as_str())
.unwrap_or(body)
.to_string();
let reason = err_obj
.and_then(|e| e.get("errors"))
.and_then(|e| e.as_array())View on GitHub (pinned to a3768d0e82)
Solutions
- Retry the command — transient truncation resolves itself.
- Check whether a proxy/captive portal intercepts googleapis.com and bypass it.
- Reproduce the raw body: `curl -H "Authorization: Bearer $TOKEN" 'https://gmail.googleapis.com/gmail/v1/users/me/messages/<ID>?format=full' | head -c 200` to see if it is HTML.
- Verify the OAuth token is a real Gmail token, not one from a mock/stub server.
Example fix
// before: json() alone loses the body that failed to parse
let msg: Value = resp.json().await.map_err(|e| GwsError::Other(anyhow::anyhow!("Failed to parse message: {e}")))?;
// after: capture the raw body to surface what was actually returned
let text = resp.text().await.map_err(|e| GwsError::Other(anyhow::anyhow!("Failed to read message body: {e}")))?;
let msg: Value = serde_json::from_str(&text).map_err(|e| {
GwsError::Other(anyhow::anyhow!("Failed to parse message: {e} (body starts: {})", &text[..text.len().min(120)]))
})?; Defensive patterns
Strategy: validation
Validate before calling
// Assert JSON content-type before parsing, catching interceptor HTML early
let ct = resp.headers().get(reqwest::header::CONTENT_TYPE).and_then(|v| v.to_str().ok()).unwrap_or("");
if !ct.starts_with("application/json") {
return Err(anyhow::anyhow!("expected JSON from Gmail, got '{ct}' — proxy interception?"));
} Type guard
fn looks_like_gmail_message(v: &serde_json::Value) -> bool {
v.get("id").and_then(|x| x.as_str()).is_some() && v.get("payload").map(|p| p.is_object()).unwrap_or(false)
} Try / catch
// Parse via text() first so failures can include a body snippet:
let text = resp.text().await?;
match serde_json::from_str::<Value>(&text) {
Ok(v) if looks_like_gmail_message(&v) => v,
Ok(_) => return Err(anyhow::anyhow!("200 response missing id/payload fields")),
Err(e) => return Err(anyhow::anyhow!("invalid JSON from Gmail: {e}; body[0..120]={}", &text[..text.len().min(120)])),
} Prevention
- Check Content-Type before json() on every Google API call you make yourself.
- Route around TLS-inspecting proxies for googleapis.com in scripts that parse responses.
- Retry once on parse failure — truncation is often transient.
When it happens
Trigger: Captive portal or proxy returning 200 with an HTML login page; response truncated by a flaky connection so the body is cut mid-JSON; a gateway rewriting responses; extremely rare API glitch serving an error page with 200.
Common situations: Corporate TLS-inspection appliances, hotel wifi, or a mid-response connection reset that reqwest still surfaces as a complete body read; scripts parsing via the CLI in sandboxed CI with weird egress.
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
- Failed to parse sendAs response: {e}
- Failed to parse attachment JSON: {e}
- Failed to parse profile: {e}
- Failed to parse People API response: {e}
- Failed to parse calendar list: {e}
AI-assisted analysis of googleworkspace/cli@a3768d0e82 (2026-08-16).
Data as JSON: /api/errors/a7c28b13cb529760.
Report an issue: GitHub.