googleworkspace/cli · error · GwsError

Failed to parse calendar list: {e}

Error message

Failed to parse calendar list: {e}

What it means

The calendarList request returned a success status, but response.json() could not deserialize the body as JSON. So the transport worked, the server said OK, yet the payload is not the expected JSON document — usually an HTML page or an empty/garbled body from an intermediary, since the Calendar API itself reliably returns JSON on 2xx.

Source

Thrown at crates/google-workspace-cli/src/helpers/calendar.rs:283

        .bearer_auth(&token)
        .send()
        .await
        .map_err(|e| GwsError::Other(anyhow::anyhow!("Failed to list calendars: {e}")))?;

    if !list_resp.status().is_success() {
        let err = list_resp.text().await.unwrap_or_default();
        return Err(GwsError::Api {
            code: 0,
            message: err,
            reason: "calendarList_failed".to_string(),
            enable_url: None,
        });
    }

    let list_json: Value = list_resp
        .json()
        .await
        .map_err(|e| GwsError::Other(anyhow::anyhow!("Failed to parse calendar list: {e}")))?;

    let calendars = list_json
        .get("items")
        .and_then(|i| i.as_array())
        .cloned()
        .unwrap_or_default();

    // 2. For each calendar, fetch events concurrently
    use futures_util::stream::{self, StreamExt};

    // Pre-filter calendars and collect owned data to avoid lifetime issues
    struct CalInfo {
        id: String,
        summary: String,
    }
    let filtered_calendars: Vec<CalInfo> = calendars
        .iter()
        .filter_map(|cal| {

View on GitHub (pinned to a3768d0e82)

Solutions

  1. Reproduce the raw body: curl -s 'https://www.googleapis.com/calendar/v3/users/me/calendarList' -H "Authorization: Bearer $TOKEN" | head -c 400 — if it is HTML, fix the proxy/network
  2. Bypass or correctly configure the intercepting proxy / captive portal
  3. If the body looks like valid JSON, report a bug with the body snippet

Example fix

// before: blind json() losing the payload on failure
let list_json: Value = list_resp.json().await.map_err(|e| GwsError::Other(anyhow::anyhow!("Failed to parse calendar list: {e}")))?;

// after: capture the body for diagnosis
let body = list_resp.text().await.unwrap_or_default();
let list_json: Value = serde_json::from_str(&body).map_err(|e| {
    GwsError::Other(anyhow::anyhow!("calendarList body was not JSON ({e}): {}", &body[..body.len().min(200)]))
})?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Fetch text first, validate JSON shape before trusting it
let body = resp.text().await?;
let v: serde_json::Value = serde_json::from_str(&body)?;
anyhow::ensure!(v.get("items").map(|i| i.is_array()).unwrap_or(false) || v.get("kind").is_some(),
    "unexpected calendarList payload (not a Calendar API document)");

Type guard

fn is_calendar_list_doc(v: &serde_json::Value) -> bool {
    v.is_object() && (v.get("items").is_some_and(|i| i.is_array()) || v.get("kind").is_some())
}

Try / catch

let body = resp.text().await.unwrap_or_default();
let parsed: serde_json::Value = match serde_json::from_str(&body) {
    Ok(v) => v,
    Err(e) => {
        // non-JSON 2xx body: proxy/captive portal — log first 200 bytes and surface a network-config error
        anyhow::bail!("calendarList returned non-JSON body ({e}): {}", &body.chars().take(200).collect::<String>())
    }
};

Prevention

When it happens

Trigger: Captive portal / proxy returning an HTML terms page with 200; a transparent proxy appending junk; empty 200 body from a misrouting gateway; response gzip/charset mangling; API edge returning an empty body on partial outage.

Common situations: Corporate web filters intercepting Google domains; hotel/airport Wi-Fi captive portals; flaky home routers with 'web acceleration'; rare Google frontend incidents.

Understand the failure class

Related errors


AI-assisted analysis of googleworkspace/cli@a3768d0e82 (2026-08-16). Data as JSON: /api/errors/0949b4bb1eab65fc. Report an issue: GitHub.