zeroclaw-labs/zeroclaw · error · anyhow::Error

Gmail OAuth token is not configured

Error message

Gmail OAuth token is not configured

What it means

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.

Source

Thrown at crates/zeroclaw-channels/src/gmail_push.rs:193

            alias: alias.into(),
            peer_resolver,
            http,
            last_history_id: Arc::new(Mutex::new(0)),
            tx: Arc::new(Mutex::new(None)),
        }
    }

    /// Register a Gmail watch subscription via `POST /gmail/v1/users/me/watch`.
    pub async fn register_watch(&self) -> Result<WatchResponse> {
        let token = self.config.oauth_token.clone();
        if token.is_empty() {
            ::zeroclaw_log::record!(
                ERROR,
                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
                    .with_outcome(::zeroclaw_log::EventOutcome::Failure),
                "Gmail OAuth token is not configured"
            );
            anyhow::bail!("Gmail OAuth token is not configured");
        }

        let body = serde_json::json!({
            "topicName": self.config.topic,
            "labelIds": self.config.label_filter,
        });

        let resp = self
            .http
            .post("https://gmail.googleapis.com/gmail/v1/users/me/watch")
            .bearer_auth(&token)
            .json(&body)
            .send()
            .await?;

        if !resp.status().is_success() {
            let status = resp.status();
            let text = resp.text().await.unwrap_or_default();

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Set a valid Gmail OAuth2 access token under [channels.gmail.<alias>] oauth_token in config.toml (needs the gmail.readonly scope for watch registration)
  2. If the token is injected from an environment variable or secret store, verify that source resolves non-empty before the channel is constructed
  3. If the channel should not run, keep enabled = false or remove the [channels.gmail.<alias>] block so the orchestrator never instantiates it
  4. 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

Example fix

# before (config.toml)
[channels.gmail.main]
enabled = true
topic = "projects/my-project/topics/gmail-push"

# after
[channels.gmail.main]
enabled = true
topic = "projects/my-project/topics/gmail-push"
oauth_token = "ya29.a0AfB..."   # Gmail OAuth2 access token
Defensive patterns

Strategy: validation

Validate before calling

// Before enabling the channel / calling register_watch:
if config.oauth_token.trim().is_empty() {
    anyhow::bail!("refusing to start gmail channel '{}': oauth_token is empty", alias);
}
channel.register_watch().await?;

Try / catch

match channel.register_watch().await {
    Ok(watch) => { /* store history_id */ }
    Err(e) if e.to_string().contains("Gmail OAuth token is not configured") => {
        // configuration defect: do not retry; surface to operator
        return Err(e.context("set [channels.gmail.<alias>] oauth_token in config.toml"));
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/6f88de9be6edce79. Report an issue: GitHub.