googleworkspace/cli · warning · GwsError

5

5

Error message

Failed to fetch account timezone: {e}

What it means

`fetch_account_timezone()`'s GET to `https://www.googleapis.com/calendar/v3/users/me/settings/timezone` failed at the transport layer (DNS, connection, TLS, proxy) before any status code existed. Note this is a *soft* failure in practice: `resolve_account_timezone()` catches it, logs a warning, and falls back to the machine-local timezone (step 4 of its priority chain), so the command usually still completes — possibly with wrong-day boundaries for users whose machine tz differs from their calendar tz.

Source

Thrown at crates/google-workspace-cli/src/timezone.rs:85

        if let Err(e) = std::fs::create_dir_all(parent) {
            tracing::warn!(path = %parent.display(), error = %e, "failed to create timezone cache directory");
            return;
        }
    }
    if let Err(e) = std::fs::write(&path, tz_name) {
        tracing::warn!(path = %path.display(), error = %e, "failed to write timezone cache");
    }
}

/// Fetch the account timezone from the Google Calendar Settings API.
async fn fetch_account_timezone(client: &reqwest::Client, token: &str) -> Result<Tz, GwsError> {
    let url = "https://www.googleapis.com/calendar/v3/users/me/settings/timezone";
    let resp = client
        .get(url)
        .bearer_auth(token)
        .send()
        .await
        .map_err(|e| GwsError::Other(anyhow::anyhow!("Failed to fetch account timezone: {e}")))?;

    if !resp.status().is_success() {
        let status = resp.status();
        let body = resp.text().await.unwrap_or_default();
        return Err(GwsError::Api {
            code: status.as_u16(),
            message: body,
            reason: "timezone_fetch_failed".to_string(),
            enable_url: None,
        });
    }

    let json: serde_json::Value = resp
        .json()
        .await
        .map_err(|e| GwsError::Other(anyhow::anyhow!("Failed to parse timezone response: {e}")))?;

    let tz_name = json

View on GitHub (pinned to a3768d0e82)

Solutions

  1. Pass `--timezone America/Denver` (any IANA name) — priority 1, skips the network fetch entirely.
  2. Restore egress to www.googleapis.com and retry; a successful fetch repopulates the 24h cache.
  3. Check proxy env vars if a middlebox is breaking the request.
  4. If you rely on calendar-day accuracy in automation, always set --timezone explicitly rather than depending on the silent local-machine fallback.

Example fix

# before — silent fallback to machine tz when offline
GOOGLE_WORKSPACE_CLI_LOG=warn gws calendar +standup-report
# stderr: failed to fetch account timezone, falling back to local

# after — pin the zone, no fetch needed
gws calendar +standup-report --timezone Europe/Berlin
Defensive patterns

Strategy: fallback

Validate before calling

// Skip the network fetch when the zone is known
if let Some(tz) = std::env::var("GWS_TZ").ok() {
    args.push(format!("--timezone={tz}")); // priority-1 override, no HTTP call
}

Try / catch

// resolve_account_timezone already implements the recommended pattern:
// fetch failure -> warn -> machine-local fallback. Wrap callers to surface it:
match timezone::resolve_account_timezone(&client, &token, override_tz).await {
    Ok(tz) => tz,
    Err(e) => chrono_tz::UTC, // belt-and-braces: never fail a report for timezone reasons
}

Prevention

When it happens

Trigger: First run of a date-aware helper (`+standup-report`, `+focus`, etc.) with no timezone cache and no `--timezone` flag while offline or behind a broken proxy; DNS failure for www.googleapis.com; ADC/service environments without external egress.

Common situations: Egress-restricted CI; cached timezone older than 24h expiring exactly when the network is down; machines set to UTC while the Google account lives in another zone (silent boundary shift after fallback).

Related errors


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