{"record":{"id":"0b5d90d1f74d96b8","repo":"googleworkspace/cli","slug":"profile-missing-emailaddress","errorCode":null,"errorMessage":"Profile missing emailAddress","messagePattern":"Profile missing emailAddress","errorType":"validation","errorClass":"GwsError","httpStatus":null,"severity":"error","filePath":"crates/google-workspace-cli/src/helpers/gmail/reply.rs","lineNumber":208,"sourceCode":"            .await\n            .unwrap_or_else(|_| \"(error body unreadable)\".to_string());\n        return Err(super::build_api_error(\n            status,\n            &body,\n            \"Failed to fetch user profile\",\n        ));\n    }\n\n    let profile: Value = resp\n        .json()\n        .await\n        .map_err(|e| GwsError::Other(anyhow::anyhow!(\"Failed to parse profile: {e}\")))?;\n\n    profile\n        .get(\"emailAddress\")\n        .and_then(|v| v.as_str())\n        .map(|s| s.to_string())\n        .ok_or_else(|| GwsError::Other(anyhow::anyhow!(\"Profile missing emailAddress\")))\n}\n\n// --- Message construction ---\n\nfn extract_reply_to_address(original: &OriginalMessage) -> Vec<Mailbox> {\n    match &original.reply_to {\n        Some(reply_to) => reply_to.clone(),\n        None => vec![original.from.clone()],\n    }\n}\n\nfn build_reply_all_recipients(\n    original: &OriginalMessage,\n    extra_cc: Option<&[Mailbox]>,\n    remove: Option<&[Mailbox]>,\n    self_email: Option<&str>,\n    from_alias: Option<&str>,\n) -> Result<ReplyRecipients, GwsError> {","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/googleworkspace/cli/blob/a3768d0e82ad83cca2da97724e46bea4ff0e6dbd/crates/google-workspace-cli/src/helpers/gmail/reply.rs#L190-L226","documentation":"The profile JSON parsed but `emailAddress` was absent or not a string — `profile.get(\"emailAddress\").and_then(|v| v.as_str())` returned None. The Gmail profile endpoint guarantees `emailAddress` for a valid token with gmail scope, so this implies the endpoint returned something else: an empty object from a scope mismatch, an error envelope with 200, a stub response, or a future API change.","triggerScenarios":"OAuth token granted for the wrong product (e.g. only People scope) causing a degenerate profile response; a mock server returning `{}`; API contract drift; a delegated/service account context where the profile resolves to an empty object.","commonSituations":"Scripts reusing a token minted for a different scope set; test fixtures that stub the profile endpoint minimally; rarely, Gmail API schema evolution.","solutions":["Verify scopes: run `gws auth status` and ensure gmail scope is granted, then `gws gmail users.getProfile --userId me` to inspect the live response.","If a stub server is in play, add `emailAddress` to the fixture.","Re-auth (`gws auth login`) if scopes are wrong or partial.","Update the CLI if Gmail changed the profile shape."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"// Check scope coverage before reply-all runs: a token without gmail scope yields degenerate profiles\nfn token_has_gmail_scope(scopes: &[String]) -> bool {\n    scopes.iter().any(|s| s.contains(\"gmail\"))\n}","typeGuard":"fn profile_has_email(v: &serde_json::Value) -> bool {\n    v.get(\"emailAddress\").and_then(|e| e.as_str()).is_some_and(|s| !s.is_empty())\n}","tryCatchPattern":"// Validate the envelope shape right after parse; fail with a scope-hint message:\nlet profile: Value = resp.json().await?;\nif !profile_has_email(&profile) {\n    return Err(anyhow::anyhow!(\"profile lacks emailAddress — token may be missing gmail scope; run gws auth login\"));\n}","preventionTips":["Run `gws gmail users.getProfile --userId me` once after auth to confirm the token yields a full profile.","Grant gmail scopes at `gws auth login`; re-login after scope changes.","Type-guard API envelopes before extracting required fields in your own Google API code."],"tags":["gmail","profile","missing-field","oauth-scopes","api-contract"],"backgroundTag":"missing-response-field","analyzedSha":"a3768d0e82ad83cca2da97724e46bea4ff0e6dbd","analyzedAt":"2026-08-16T19:51:46.516Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}