{"record":{"id":"b8b77a50793af91e","repo":"astrid-runtime/astrid","slug":"idle-timeout-must-be-greater-than-0-seconds","errorCode":null,"errorMessage":"idle timeout must be greater than 0 seconds","messagePattern":"idle timeout must be greater than 0 seconds","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/headless.rs","lineNumber":44,"sourceCode":"pub(crate) enum HeadlessError {\n    /// No active-run message arrived within the configured idle budget.\n    #[error(\"timed out waiting for response after {timeout_secs}s idle\")]\n    IdleTimeout {\n        /// The configured idle budget in whole seconds.\n        timeout_secs: u64,\n    },\n    /// The daemon connection failed while collecting the response.\n    #[error(transparent)]\n    Read(#[from] anyhow::Error),\n}\n\n/// Validate and convert a whole-second run idle timeout.\n///\n/// # Errors\n/// Returns an error for zero or values above the one-day operational ceiling.\npub(crate) fn idle_timeout(timeout_secs: u64) -> Result<Duration> {\n    if timeout_secs == 0 {\n        anyhow::bail!(\"idle timeout must be greater than 0 seconds\");\n    }\n    if timeout_secs > MAX_RUN_IDLE_TIMEOUT_SECS {\n        anyhow::bail!(\"idle timeout must be at most {MAX_RUN_IDLE_TIMEOUT_SECS} seconds\");\n    }\n    Ok(Duration::from_secs(timeout_secs))\n}\n\n/// Source of daemon messages consumed by headless response collection.\npub(crate) trait ResponseSource {\n    /// Read the next daemon message or report timeout at the source boundary.\n    fn read_message_before(\n        &mut self,\n        remaining: Duration,\n    ) -> impl Future<Output = ReadOutcome> + Send;\n\n    /// Send a message to the daemon.\n    fn send_message(\n        &mut self,","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/headless.rs#L26-L62","documentation":"The headless runner's idle_timeout converts a whole-second CLI value into a Duration. Zero is rejected because a 0-second idle timeout would invalidate runs immediately — the run must be given a positive idle window.","triggerScenarios":"Invoking the headless command (or calling idle_timeout directly) with --idle-timeout 0 or an equivalent zero-second value.","commonSituations":"Scripts parameterizing the timeout where a computed value evaluates to 0; users passing 0 assuming it means 'no timeout' (it does not); copy-pasted flags with a placeholder left at 0.","solutions":["Pass a positive integer of seconds, e.g. --idle-timeout 60.","If 'no timeout' is intended, use the maximum allowed value (up to the one-day ceiling) rather than 0.","Fix the script/flag source so the computed timeout is never 0."],"exampleFix":"// before\nastrid headless --idle-timeout 0\n\n// after\nastrid headless --idle-timeout 300","handlingStrategy":"validation","validationCode":"fn idle_timeout_ok(secs: u64) -> bool {\n    secs > 0 && secs <= 86400\n}\n// guard before invoking the CLI:\nassert!(idle_timeout_ok(secs), \"idle timeout must be 1..=86400 seconds\");","typeGuard":"fn parse_idle_timeout(raw: &str) -> Option<u64> {\n    raw.parse::<u64>().ok().filter(|s| *s > 0 && *s <= 86400)\n}","tryCatchPattern":"match idle_timeout(secs) {\n    Ok(d) => run_with_timeout(d).await,\n    Err(e) => eprintln!(\"astrid: {e}; pass a whole number of seconds in 1..=86400\"),\n}","preventionTips":["Clamp computed timeouts to >= 1 second in scripts.","Document that 0 is invalid — use the max value instead of 0 for long windows.","Validate flags at the script boundary before shelling out to the CLI."],"tags":["cli","timeout","validation"],"backgroundTag":"value-out-of-range","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}