Hmbown/CodeWhale · error

fleet alert secret is not configured

Error message

fleet alert secret {name} is not configured

What it means

When a fleet alert adapter sends for real (not dry_run), required_secret() asks the FleetAlertSecretResolver for a named secret — by default FleetEnvSecretResolver, which reads an environment variable and treats empty strings as missing. A None resolve produces this error, naming the env var that was expected. It guards PagerDuty routing keys and webhook secret_env headers (X-CodeWhale-Webhook-Secret).

Solutions

  1. Set the named variable to a non-empty value in the exact environment the fleet dispatcher runs in: e.g. export PAGERDUTY_ROUTING_KEY=... (or match whatever name config references).
  2. Verify spelling/case of the *_env field in the adapter config against the variable you exported; print the resolver name from the error to confirm.
  3. If the variable is intentionally optional, remove the secret_env/routing_key_env field from the adapter config so required_secret is never called for it.
  4. Run one dispatch with dry_run=true first — dry-run prepares payloads without resolving send-time secrets where avoidable, confirming the rest of the config.

Example fix

# before
# config: {"kind":"pagerduty","routing_key_env":"PAGERDUTY_KEY"}
$ codewhale fleet ...   # -> fleet alert secret PAGERDUTY_KEY is not configured

# after
$ export PAGERDUTY_KEY="deadbeef..." && codewhale fleet ...
Defensive patterns

Strategy: validation

Validate before calling

fn required_alert_env_vars(cfg: &FleetAlertConfig) -> Vec<String> {
    let mut names = Vec::new();
    if cfg.dry_run { return names; }
    for adapter in cfg.adapters.values() {
        match adapter {
            FleetAlertAdapterConfig::PagerDuty { routing_key_env, .. } => names.push(routing_key_env.clone()),
            FleetAlertAdapterConfig::Webhook { secret_env: Some(s), .. } => names.push(s.clone()),
            _ => {}
        }
    }
    names.retain(|n| std::env::var(n).map(|v| !v.is_empty()).unwrap_or(false));
    names // non-empty = OK; diff against the full list to find missing ones
}

Try / catch

if let Err(e) = dispatcher.dispatch(&event) {
    let msg = e.to_string();
    if msg.contains("secret") && msg.contains("not configured") {
        // parse the var name out of the message, prompt/operator to set it, skip alerting
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: Dispatching a real (dry_run=false) alert through PagerDuty with routing_key_env unset/empty, or a Webhook adapter whose secret_env variable is unset, empty, or only set in a different shell/session/service unit.

Common situations: Env var set in an interactive shell but not in the systemd/launchd/tmux environment the TUI runs under; variable name typo between config and environment; CI runners lacking secrets; values consisting only of whitespace (env resolver only filters truly empty strings, but shell 'export VAR=' sets empty).

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/d354ff7198023a33. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/fleet/alerts.rs:489

        FleetAlertEventClass::VerifierFailed => "verifier_failed",
        FleetAlertEventClass::RunCompleted => "run_completed",
    }
}

fn redacted_secret_header(secret_env: Option<&str>) -> Value {
    match secret_env {
        Some(name) => json!({ "X-CodeWhale-Webhook-Secret": redacted_env(name) }),
        None => json!({}),
    }
}

fn required_secret<R>(resolver: &R, name: &str) -> Result<String>
where
    R: FleetAlertSecretResolver,
{
    resolver
        .resolve(name)
        .ok_or_else(|| anyhow!("fleet alert secret {name} is not configured"))
}

fn required_https_url<R>(resolver: &R, name: &str) -> Result<String>
where
    R: FleetAlertSecretResolver,
{
    let url = resolver
        .resolve(name)
        .ok_or_else(|| anyhow!("fleet alert URL {name} is not configured"))?;
    validate_https_alert_url(name, &url)?;
    Ok(url)
}

fn validate_https_alert_url(name: &str, url: &str) -> Result<()> {
    let parsed = reqwest::Url::parse(url)
        .with_context(|| format!("fleet alert URL from {name} is not a valid URL"))?;
    if parsed.scheme() != "https" {
        return Err(anyhow!("fleet alert URL from {name} must use https"));

View on GitHub (pinned to 0c42157ee5)