{"record":{"id":"63fdcc4b96f31511","repo":"zeroclaw-labs/zeroclaw","slug":"delivery-channel-is-required-for-announce-mode","errorCode":null,"errorMessage":"delivery.channel is required for announce mode","messagePattern":"delivery\\.channel is required for announce mode","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-runtime/src/cron/mod.rs","lineNumber":150,"sourceCode":"        shell_output_format,\n    )\n}\n\npub fn validate_delivery_config(delivery: Option<&DeliveryConfig>) -> Result<()> {\n    let Some(delivery) = delivery else {\n        return Ok(());\n    };\n\n    if delivery.mode.eq_ignore_ascii_case(\"none\") {\n        return Ok(());\n    }\n    if !delivery.mode.eq_ignore_ascii_case(\"announce\") {\n        bail!(\"unsupported delivery mode: {}\", delivery.mode);\n    }\n\n    let channel = delivery.channel.as_deref().map(str::trim);\n    if channel.filter(|value| !value.is_empty()).is_none() {\n        bail!(\"delivery.channel is required for announce mode\");\n    }\n\n    let has_target = delivery\n        .to\n        .as_deref()\n        .map(str::trim)\n        .is_some_and(|value| !value.is_empty());\n    if !has_target {\n        bail!(\"delivery.to is required for announce mode\");\n    }\n\n    Ok(())\n}\n\npub fn add_shell_job_with_approval(\n    config: &Config,\n    agent_alias: &str,\n    name: Option<String>,","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/cron/mod.rs#L132-L168","documentation":"After the mode whitelist passes, announce mode must name a channel: delivery.channel must be present and non-empty after trimming. Missing, empty, or whitespace-only channel bails before target validation. A delivery.to target alone is not sufficient — the channel is required for announce.","triggerScenarios":"delivery = { mode: 'announce' } with channel omitted, set to \"\", or set to spaces; programmatically built configs that populate only the `to` field.","commonSituations":"Hand-written cron YAML/TOML forgetting the channel field; templates assuming a default channel; field renames during refactors leaving channel unset.","solutions":["Set delivery.channel to a configured, non-empty channel identifier","Trim channel values if config sources may carry whitespace","If no announcement is wanted, use mode 'none' instead of announce-without-channel","Validate the whole delivery block in config loading, not just at job creation"],"exampleFix":"// before\ndelivery = { mode: \"announce\", channel: None, to: Some(\"@ops\") }\n\n// after\ndelivery = { mode: \"announce\", channel: Some(\"telegram:ops\".into()), to: Some(\"@ops\") }","handlingStrategy":"validation","validationCode":"if !delivery_is_valid(&delivery) {\n    anyhow::bail!(\"announce mode requires a non-empty delivery.channel\");\n}\ncron.add_agent_job(spec, delivery).await?;","typeGuard":"fn delivery_is_valid(d: &DeliveryConfig) -> bool {\n    if d.mode.eq_ignore_ascii_case(\"none\") { return true; }\n    if !d.mode.eq_ignore_ascii_case(\"announce\") { return false; }\n    d.channel.as_deref().map(|c| !c.trim().is_empty()).unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Templates for announce-mode jobs must include the channel field","Use mode 'none' when no announcement is wanted rather than leaving channel blank","Validate the full delivery block in one place before job creation","Trim channel values from user-edited configs"],"tags":["cron","delivery-config","required-field","announce-mode"],"backgroundTag":"missing-required-config-field","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}