{"record":{"id":"6f88de9be6edce79","repo":"zeroclaw-labs/zeroclaw","slug":"gmail-oauth-token-is-not-configured","errorCode":null,"errorMessage":"Gmail OAuth token is not configured","messagePattern":"Gmail OAuth token is not configured","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/gmail_push.rs","lineNumber":193,"sourceCode":"            alias: alias.into(),\n            peer_resolver,\n            http,\n            last_history_id: Arc::new(Mutex::new(0)),\n            tx: Arc::new(Mutex::new(None)),\n        }\n    }\n\n    /// Register a Gmail watch subscription via `POST /gmail/v1/users/me/watch`.\n    pub async fn register_watch(&self) -> Result<WatchResponse> {\n        let token = self.config.oauth_token.clone();\n        if token.is_empty() {\n            ::zeroclaw_log::record!(\n                ERROR,\n                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)\n                    .with_outcome(::zeroclaw_log::EventOutcome::Failure),\n                \"Gmail OAuth token is not configured\"\n            );\n            anyhow::bail!(\"Gmail OAuth token is not configured\");\n        }\n\n        let body = serde_json::json!({\n            \"topicName\": self.config.topic,\n            \"labelIds\": self.config.label_filter,\n        });\n\n        let resp = self\n            .http\n            .post(\"https://gmail.googleapis.com/gmail/v1/users/me/watch\")\n            .bearer_auth(&token)\n            .json(&body)\n            .send()\n            .await?;\n\n        if !resp.status().is_success() {\n            let status = resp.status();\n            let text = resp.text().await.unwrap_or_default();","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/gmail_push.rs#L175-L211","documentation":"Thrown by GmailPushChannel::register_watch before any HTTP traffic: it clones GmailPushConfig.oauth_token, finds it an empty string, and bails. The token is the oauth_token field of the [channels.gmail.<alias>] block in config.toml (serde default is empty, and the field is marked #[secret]), so a channel can be constructed and even enabled with no credential at all. This is a pure configuration error, not a Google-side or network failure.","triggerScenarios":"Calling register_watch() directly, or letting Channel::listen / the orchestrator start a channel, when config.oauth_token.is_empty() — e.g. GmailPushConfig::default(), a [channels.gmail.<alias>] block that sets enabled = true and topic but omits oauth_token, or a token sourced from an env/secret variable that resolved to an empty string at construction time.","commonSituations":"Operator enables the Gmail push channel before finishing OAuth setup; config.toml generated from a template with the secret left blank; the env var feeding oauth_token missing in systemd/containers; unit tests constructing GmailPushChannel::new(GmailPushConfig::default(), ...) and touching register_watch.","solutions":["Set a valid Gmail OAuth2 access token under [channels.gmail.<alias>] oauth_token in config.toml (needs the gmail.readonly scope for watch registration)","If the token is injected from an environment variable or secret store, verify that source resolves non-empty before the channel is constructed","If the channel should not run, keep enabled = false or remove the [channels.gmail.<alias>] block so the orchestrator never instantiates it","Add a fail-fast startup check: any enabled channel whose oauth_token is empty should abort startup with a clear message instead of failing later inside register_watch"],"exampleFix":"# before (config.toml)\n[channels.gmail.main]\nenabled = true\ntopic = \"projects/my-project/topics/gmail-push\"\n\n# after\n[channels.gmail.main]\nenabled = true\ntopic = \"projects/my-project/topics/gmail-push\"\noauth_token = \"ya29.a0AfB...\"   # Gmail OAuth2 access token","handlingStrategy":"validation","validationCode":"// Before enabling the channel / calling register_watch:\nif config.oauth_token.trim().is_empty() {\n    anyhow::bail!(\"refusing to start gmail channel '{}': oauth_token is empty\", alias);\n}\nchannel.register_watch().await?;","typeGuard":null,"tryCatchPattern":"match channel.register_watch().await {\n    Ok(watch) => { /* store history_id */ }\n    Err(e) if e.to_string().contains(\"Gmail OAuth token is not configured\") => {\n        // configuration defect: do not retry; surface to operator\n        return Err(e.context(\"set [channels.gmail.<alias>] oauth_token in config.toml\"));\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Treat an enabled channel with an empty oauth_token as a startup error, not a runtime one — validate at boot","Keep secrets out of git: source oauth_token via your secret store and assert it resolves before constructing GmailPushChannel","Never construct production channels from GmailPushConfig::default() — it ships an empty token by design"],"tags":["gmail","oauth-token","configuration","zeroclaw-channels","rust"],"backgroundTag":"missing-oauth-token","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}