{"record":{"id":"e9db854bba6fecbc","repo":"tinyhumansai/openhuman","slug":"invalid-cron-expression-expression-expected-5","errorCode":null,"errorMessage":"Invalid cron expression: {expression} (expected 5, 6, or 7 fields, got {field_count})","messagePattern":"Invalid cron expression: (.+?) \\(expected 5, 6, or 7 fields, got (.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/openhuman/cron/schedule.rs","lineNumber":187,"sourceCode":"        if self.start <= self.end {\n            time >= self.start && time <= self.end\n        } else {\n            // Window spans midnight (e.g. 22:00 to 06:00).\n            time >= self.start || time <= self.end\n        }\n    }\n}\n\npub fn normalize_expression(expression: &str) -> Result<String> {\n    let expression = expression.trim();\n    let field_count = expression.split_whitespace().count();\n\n    match field_count {\n        // standard crontab syntax: minute hour day month weekday\n        5 => Ok(format!(\"0 {expression}\")),\n        // crate-native syntax includes seconds (+ optional year)\n        6 | 7 => Ok(expression.to_string()),\n        _ => anyhow::bail!(\n            \"Invalid cron expression: {expression} (expected 5, 6, or 7 fields, got {field_count})\"\n        ),\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use chrono::TimeZone;\n\n    #[test]\n    fn next_run_for_schedule_supports_every_and_at() {\n        let now = Utc::now();\n        let every = Schedule::Every { every_ms: 60_000 };\n        let next = next_run_for_schedule(&every, now).unwrap();\n        assert!(next > now);\n\n        let at = now + ChronoDuration::minutes(10);","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/cron/schedule.rs#L169-L205","documentation":"normalize_expression counts whitespace-separated fields: 5 fields (standard crontab: minute hour day month weekday) are accepted and auto-prefixed with a seconds field '0'; 6 (with seconds) or 7 (seconds + year) are crate-native and used verbatim. Any other count is rejected as malformed before the cron parser ever sees it.","triggerScenarios":"Submitting '*/5 * * *' (4 fields, weekday missing), an 8-field expression, or a full crontab line with its command tail pasted in ('*/5 * * * * /usr/bin/backup' — 6 fields, the 6th being garbage downstream). Leading/trailing/multiple spaces are fine (trim + split_whitespace).","commonSituations":"Hand-writing cron and dropping a field; copying a whole crontab line including the command; adding seconds to a 5-field expression creating an unintended reading; expecting @daily-style shorthands to work (they are just words here).","solutions":["Count your fields: write exactly 5 (standard) or 6/7 (seconds, optional year) whitespace-separated fields","Strip any command/argument tail — the command lives in the job's separate command/prompt field","Prefer the 5-field form; the core prepends the seconds field automatically"],"exampleFix":"# before (command pasted into the expression)\n\"*/5 * * * * /usr/bin/backup\"\n\n# after\n\"*/5 * * * *\"  # command goes in the job's command/prompt field","handlingStrategy":"validation","validationCode":"fn looks_like_valid_cron(expr: &str) -> bool {\n    matches!(expr.split_whitespace().count(), 5 | 6 | 7)\n}","typeGuard":"function isValidCronExpression(expr: string): boolean {\n  const fields = expr.trim().split(/\\s+/).length;\n  return fields >= 5 && fields <= 7;\n}","tryCatchPattern":null,"preventionTips":["Use a cron expression builder/picker UI that emits exactly 5 fields","Never paste the command part of a crontab line into the expression field","Add a field-count check next to every cron input in forms and scripts"],"tags":["cron","expression","parsing","validation"],"backgroundTag":"cron-expression-invalid","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}