{"record":{"id":"611968e1cf1fb978","repo":"tinyhumansai/openhuman","slug":"delay-must-not-be-empty","errorCode":null,"errorMessage":"delay must not be empty","messagePattern":"delay must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/openhuman/cron/ops.rs","lineNumber":134,"sourceCode":"        }\n    }\n\n    let patch = CronJobPatch {\n        schedule,\n        command,\n        name,\n        ..CronJobPatch::default()\n    };\n\n    update_job(config, id, patch)\n}\n\n/// Parse a human-friendly delay string (e.g. \"5m\", \"2h\", \"30s\") into a\n/// `chrono::Duration`. Defaults to minutes when no unit is given.\npub fn parse_human_delay(input: &str) -> Result<chrono::Duration> {\n    let input = input.trim();\n    if input.is_empty() {\n        anyhow::bail!(\"delay must not be empty\");\n    }\n    let split = input\n        .find(|c: char| !c.is_ascii_digit())\n        .unwrap_or(input.len());\n    let (num, unit) = input.split_at(split);\n    let amount: i64 = num.parse()?;\n    let unit = if unit.is_empty() { \"m\" } else { unit };\n    let duration = match unit {\n        \"s\" => chrono::Duration::seconds(amount),\n        \"m\" => chrono::Duration::minutes(amount),\n        \"h\" => chrono::Duration::hours(amount),\n        \"d\" => chrono::Duration::days(amount),\n        _ => anyhow::bail!(\"unsupported delay unit '{unit}', use s/m/h/d\"),\n    };\n    Ok(duration)\n}\n\npub async fn cron_list(config: &Config) -> Result<RpcOutcome<Vec<CronJob>>, String> {","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/cron/ops.rs#L116-L152","documentation":"parse_human_delay trims its input and rejects an empty string before splitting number from unit. An empty delay has no meaning; without the guard the split/parse would surface as a confusing integer-parse error instead.","triggerScenarios":"CLI `openhuman cron once --delay \"\"` or a whitespace-only value; RPC callers forwarding an unvalidated empty form field; scripts passing an unset shell variable that expands to an empty argument.","commonSituations":"Optional form field submitted blank; env var referenced but never exported; quoting like \"$DELAY\" with DELAY unset passing '' through.","solutions":["Pass a concrete delay: 30s, 5m, 2h, 1d (unit defaults to minutes when omitted)","Treat empty as 'no delay' upstream — skip the call or apply a default before invoking","In shell wrappers use ${DELAY:-5m} so an unset variable never reaches the CLI"],"exampleFix":"# before\nDELAY=\"\"; openhuman cron once --delay \"$DELAY\" prompt.txt\n\n# after\nDELAY=\"${DELAY:-5m}\"; openhuman cron once --delay \"$DELAY\" prompt.txt","handlingStrategy":"validation","validationCode":"fn parseable_delay(s: &str) -> bool {\n    let t = s.trim();\n    !t.is_empty() && t.start_with(|c: char| c.is_ascii_digit())\n}","typeGuard":"function isValidDelay(input: string): boolean {\n  const t = input.trim();\n  return t.length > 0 && /^\\d/.test(t);\n}","tryCatchPattern":null,"preventionTips":["Default optional delay inputs at the edge (UI or script), never inside the core call","Trim and reject blank strings in form validation","Use shell parameter expansion defaults (${DELAY:-5m}) in CLI wrappers"],"tags":["cron","delay","parsing","input-validation"],"backgroundTag":"invalid-duration-format","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}