googleworkspace/cli · error · anyhow::Error
Failed to fetch Discovery Document for {service}/{version}:
Error message
Failed to fetch Discovery Document for {service}/{version}: HTTP {} (tried both standard and $discovery URLs) What it means
This error is thrown by fetch_discovery_document() after BOTH attempts to download a Google Discovery Document returned a non-2xx HTTP status: first the standard registry URL https://www.googleapis.com/discovery/v1/apis/{service}/{version}/rest, then the fallback https://{service}.googleapis.com/$discovery/rest?version={version} used by newer APIs (Forms, Keep, Meet). The status code in the message is from the second ($discovery) attempt. It means the CLI resolved a service alias but Google's endpoints rejected the request, so no command tree can be built for that service.
Source
Thrown at crates/google-workspace/src/discovery.rs:240
crate::validate::encode_path_segment(version),
);
tracing::debug!(service = %service, version = %version, "Fetching discovery document");
let client = crate::client::build_client()?;
let resp = client.get(&url).send().await?;
let body = if resp.status().is_success() {
resp.text().await?
} else {
// Try the $discovery/rest URL pattern used by newer APIs (Forms, Keep, Meet, etc.)
let alt_url = format!("https://{service}.googleapis.com/$discovery/rest");
let alt_resp = client
.get(&alt_url)
.query(&[("version", version)])
.send()
.await?;
if !alt_resp.status().is_success() {
anyhow::bail!(
"Failed to fetch Discovery Document for {service}/{version}: HTTP {} (tried both standard and $discovery URLs)",
alt_resp.status()
);
}
alt_resp.text().await?
};
// Write to cache
if let Some(dir) = cache_dir {
let cache_file = dir.join(format!("{service}_{version}.json"));
if let Err(e) = tokio::fs::write(&cache_file, &body).await {
tracing::warn!(error = %e, "Failed to write discovery cache");
}
}
let doc: RestDescription = serde_json::from_str(&body)?;
Ok(doc)
}View on GitHub (pinned to a3768d0e82)
Solutions
- Run with GOOGLE_WORKSPACE_CLI_LOG=gws=debug to see the exact URLs and statuses tried, then curl both URLs (https://www.googleapis.com/discovery/v1/apis/{service}/{version}/rest and https://{service}.googleapis.com/$discovery/rest?version={version}) to see which status comes back
- Check crates/google-workspace/src/services.rs: confirm the alias maps to the exact API name and a currently-published version (compare with the service's Google API docs page)
- If a proxy/firewall is involved, allow-list www.googleapis.com and *.googleapis.com, or set HTTPS_PROXY to a proxy that permits those hosts
- If the service genuinely serves only the $discovery pattern, verify the host format https://{service}.googleapis.com matches the service's canonical hostname (some services differ, e.g. different API name vs hostname)
- Clear the discovery cache only if you suspect corruption: the 24h cache in the configured cache dir is written solely from successful responses, so a stale-bad cache is unlikely — prefer fixing the URL/mapping
Example fix
// crates/google-workspace/src/services.rs — before
("forms", "forms", "v2"), // 404: only v1 exists
// after
("forms", "forms", "v1"), Defensive patterns
Strategy: retry
Validate before calling
use google_workspace::services;
let alias = "forms";
// Fail fast if the alias is unknown before touching the network
let (api, version) = services::resolve(alias) // or your alias map lookup
.unwrap_or_else(|| panic!("unknown service alias {alias}"));
// Optional reachability pre-flight
// curl-equivalent: HEAD https://www.googleapis.com/discovery/v1/apis/{api}/{version}/rest Try / catch
match google_workspace::discovery::fetch_discovery_document(api, version, cache).await {
Ok(doc) => { /* build commands */ }
Err(e) if e.to_string().contains("Failed to fetch Discovery Document") => {
eprintln!("service {api}/{version} unavailable: check alias/version in services.rs and network egress to googleapis.com");
std::process::exit(2);
}
Err(e) => return Err(e.into()),
} Prevention
- Keep the alias map in crates/google-workspace/src/services.rs in sync with published API versions; add a unit test asserting each registered alias resolves to a URL that returned 200 in a recorded fixture
- Allow-list www.googleapis.com and *.googleapis.com in corporate proxies and set HTTPS_PROXY so the retrying client in client.rs can use it
- Run with GOOGLE_WORKSPACE_CLI_LOG=debug when adding a new service to see which of the two URL patterns the API actually serves
When it happens
Trigger: Calling gws with a service whose alias exists in services.rs but whose version string is wrong or retired (404); using a service that is registered in neither the Discovery registry nor the $discovery endpoint (404); a corporate proxy or captive portal returning 403/407 for googleapis.com; Google API deprecating a version (410 gone); regional firewall returning 403. Note this fires only on HTTP status failures — a connection failure surfaces as a reqwest error from .send() instead.
Common situations: Typo'd or outdated version in crates/google-workspace/src/services.rs after adding a new service alias; running in CI behind an egress proxy that blocks googleapis.com; using a stale service mapping after Google renamed/retired an API; air-gapped or DNS-hijacked networks where the proxy answers with an error page.
Related errors
- Unsupported HTTP method: {other}
- Failed to list calendars: {e}
- 5
- Token refresh failed with status {}: {}
- Pub/Sub pull failed: {e}
AI-assisted analysis of googleworkspace/cli@a3768d0e82 (2026-08-16).
Data as JSON: /api/errors/e74704962bf18b02.
Report an issue: GitHub.