Repository navigation
Open
Description
Summary
gws calendar +agenda says that it shows events across all calendars, but it can silently omit calendars and events.
At commit a3768d0e82ad83cca2da97724e46bea4ff0e6dbd:
- The helper reads only the first
calendarListpage and ignoresnextPageToken:cli/crates/google-workspace-cli/src/helpers/calendar.rs
Lines 261 to 289 in a3768d0
// 1. List all calendars let list_url = "https://www.googleapis.com/calendar/v3/users/me/calendarList"; let list_resp = client .get(list_url) .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(); - Each calendar request hard-codes
maxResults=50and ignores the eventnextPageToken:cli/crates/google-workspace-cli/src/helpers/calendar.rs
Lines 322 to 359 in a3768d0
let mut all_events: Vec<Value> = stream::iter(filtered_calendars) .map(|cal| { let client = &client; let token = &token; let time_min = &time_min; let time_max = &time_max; async move { let events_url = format!( "https://www.googleapis.com/calendar/v3/calendars/{}/events", crate::validate::encode_path_segment(&cal.id), ); let resp = crate::client::send_with_retry(|| { client .get(&events_url) .query(&[ ("timeMin", time_min.as_str()), ("timeMax", time_max.as_str()), ("singleEvents", "true"), ("orderBy", "startTime"), ("maxResults", "50"), ]) .bearer_auth(token) }) .await; let resp = match resp { Ok(r) if r.status().is_success() => r, _ => return vec![], }; let events_json: Value = match resp.json().await { Ok(v) => v, Err(_) => return vec![], }; let mut events = Vec::new(); if let Some(items) = events_json.get("items").and_then(|i| i.as_array()) { - A request error, non-success response, or JSON decode error is converted to an empty event list:
cli/crates/google-workspace-cli/src/helpers/calendar.rs
Lines 334 to 356 in a3768d0
let resp = crate::client::send_with_retry(|| { client .get(&events_url) .query(&[ ("timeMin", time_min.as_str()), ("timeMax", time_max.as_str()), ("singleEvents", "true"), ("orderBy", "startTime"), ("maxResults", "50"), ]) .bearer_auth(token) }) .await; let resp = match resp { Ok(r) if r.status().is_success() => r, _ => return vec![], }; let events_json: Value = match resp.json().await { Ok(v) => v, Err(_) => return vec![], };
This makes a partial result indistinguishable from a complete agenda with no events.
Reproduction
- Put more than 50 matching events in one calendar within the requested range.
- Run
gws calendar +agendafor that range. - The output contains at most 50 events from that calendar and reports no truncation.
A provider error for one secondary calendar also produces a successful result that omits that calendar.
Expected behavior
- Follow
nextPageTokenforcalendarList.listand everyevents.listrequest until it is absent. - Do not convert per-calendar failures into empty calendars. Return an error, or return an explicit partial-result status with the failed calendar and cause.
- If the command has an intentional caller limit, expose the limit and report that the result is truncated.
Metadata
Metadata
Assignees
Labels
No labels
Activity
manrods commentedon Aug 21, 2026
Can i work on this issue?
fix(calendar): request +agenda events in the account time zone
fix(calendar): sort +agenda by start instant, not by string
fix(calendar): report +agenda calendars that fail instead of dropping…
fix(calendar): follow nextPageToken in +agenda