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
- 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).
- 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.
- 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.
- 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
- Set alert secrets in the service/daemon environment definition (systemd Environment=, CI secret, launcher env), not ad-hoc shells.
- Prefer one *_env name per secret across all adapters to avoid per-route drift.
- Add a startup probe that resolves every configured secret before the first dispatch.
- Treat empty-string exports as unset — the resolver filters them.
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
- fleet alert URL is not configured
- Fleet alert secret is not configured
- Fleet alert URL is not configured
- could not resolve home directory for FileKeyringStore
- fleet alert adapter is not configured
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)