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

email channel '{}' has oauth2 configured but no auth service

Error message

email channel '{}' has oauth2 configured but no auth service was wired in

What it means

The email channel's config carries an oauth2 block, but the channel instance was built without an auth service (zeroclaw_providers::auth::AuthService) — the only component that can mint OAuth2 tokens. get_oauth2_token bails when it finds oauth2 config but no service, and this surfaces from connect_imap, so the channel cannot connect.

Source

Thrown at crates/zeroclaw-channels/src/email_channel.rs:242

                .to_string();

            attachments.push(zeroclaw_api::media::MediaAttachment {
                file_name,
                data,
                mime_type: mime_str,
            });
        }
        attachments
    }

    /// Attempt to obtain a bearer token via the auth service for XOAUTH2.
    /// Returns `Ok(None)` when no oauth2 config is set on this channel.
    async fn get_oauth2_token(&self) -> Result<Option<String>> {
        let Some(ref oauth2) = self.config.oauth2 else {
            return Ok(None);
        };
        let Some(ref auth_service) = self.auth_service else {
            anyhow::bail!(
                "email channel '{}' has oauth2 configured but no auth service was wired in",
                self.alias
            );
        };
        let channel_key = format!("email.{}", self.alias);
        auth_service
            .get_valid_email_oauth2_token(
                &channel_key,
                None,
                &oauth2.token_url,
                &oauth2.client_id,
                &oauth2.scopes,
            )
            .await
    }

    /// Connect to IMAP server with TLS and authenticate
    async fn connect_imap(&self) -> Result<ImapSession> {

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Wire the auth service exactly as the orchestrator does: channel.with_auth_service(Arc::new(AuthService::from_config(&config)))
  2. Or stop using OAuth2: remove the oauth2 block from channels.email.<alias> and use password/credentials auth
  3. If embedding, mirror the orchestrator wiring rather than constructing EmailChannel bare

Example fix

// before
let channel = EmailChannel::new(alias, config); // oauth2 set, no auth service → bail

// after
let auth = Arc::new(zeroclaw_providers::auth::AuthService::from_config(&config));
let channel = EmailChannel::new(alias, config).with_auth_service(auth);
Defensive patterns

Strategy: validation

Validate before calling

// Rust — validate wiring before connecting
if email_config.oauth2.is_some() && auth_service.is_none() {
    anyhow::bail!(
        "email channel '{alias}' cannot use oauth2: construct it with with_auth_service()"
    );
}
email_channel.connect_imap().await?;

Prevention

When it happens

Trigger: Constructing EmailChannel without calling with_auth_service (the builder leaves auth_service = None) while config.oauth2 is set, then running connect_imap. The standard orchestrator wires AuthService::from_config via with_auth_service; alternate constructors and test harnesses that skip it hit the bail.

Common situations: Embedding the email channel in a custom binary or test harness that builds channels directly; refactors that drop the with_auth_service call while oauth2 config remains in place.

Related errors


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