{"record":{"id":"5047c235eea9cbf1","repo":"tinyhumansai/openhuman","slug":"missing-schedule-and-legacy-expression-for-cron-jo","errorCode":null,"errorMessage":"Missing schedule and legacy expression for cron job","messagePattern":"Missing schedule and legacy expression for cron job","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/openhuman/cron/store.rs","lineNumber":718,"sourceCode":"            Some(raw) => Some(parse_rfc3339(&raw).map_err(sql_conversion_error)?),\n            None => None,\n        },\n        last_status: row.get(15)?,\n        last_output: row.get(16)?,\n    })\n}\n\nfn decode_schedule(schedule_raw: Option<&str>, expression: &str) -> Result<Schedule> {\n    if let Some(raw) = schedule_raw {\n        let trimmed = raw.trim();\n        if !trimmed.is_empty() {\n            return serde_json::from_str(trimmed)\n                .with_context(|| format!(\"Failed to parse cron schedule JSON: {trimmed}\"));\n        }\n    }\n\n    if expression.trim().is_empty() {\n        anyhow::bail!(\"Missing schedule and legacy expression for cron job\")\n    }\n\n    Ok(Schedule::Cron {\n        expr: expression.to_string(),\n        tz: None,\n        active_hours: None,\n    })\n}\n\nfn decode_delivery(delivery_raw: Option<&str>) -> Result<DeliveryConfig> {\n    if let Some(raw) = delivery_raw {\n        let trimmed = raw.trim();\n        if !trimmed.is_empty() {\n            return serde_json::from_str(trimmed)\n                .with_context(|| format!(\"Failed to parse cron delivery JSON: {trimmed}\"));\n        }\n    }\n    Ok(DeliveryConfig::default())","sourceCodeStart":700,"sourceCodeEnd":736,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/cron/store.rs#L700-L736","documentation":"decode_schedule reconstructs a job's Schedule from the DB row: the structured schedule JSON column when present and non-empty, otherwise the legacy expression column. When both are empty the row cannot yield any schedule and decoding bails — this is a corrupted or partially-written cron_jobs row, not a user input error.","triggerScenarios":"Rows touched by hand-edited cron.db (an UPDATE that cleared expression), a failed or partial migration, an interrupted restore, or a writer that inserted a job without either field. Any later cron list/get over the table hits the bail during row mapping.","commonSituations":"Manual sqlite3 surgery on the workspace DB; backup/sync tools copying the DB mid-write; importing jobs from an old version whose writer format changed.","solutions":["Repair the row: set a valid schedule JSON (or a legacy expression) via sqlite3 against the workspace's cron.db","If the job is dispensable, DELETE the broken row and re-create the job through CLI/RPC","If many rows are affected, restore the DB from backup rather than patching row by row"],"exampleFix":"-- before: row has empty schedule AND empty expression\n-- after: repair with a valid schedule JSON\nUPDATE cron_jobs\nSET schedule = '{\"type\":\"cron\",\"expr\":\"0 9 * * *\",\"tz\":null,\"active_hours\":null}'\nWHERE id = 'broken-job-id';","handlingStrategy":"try-catch","validationCode":"-- integrity check runnable before operations\nSELECT id FROM cron_jobs\nWHERE COALESCE(TRIM(schedule), '') = ''\n  AND COALESCE(TRIM(expression), '') = '';\n-- any rows returned will fail decode_schedule","typeGuard":null,"tryCatchPattern":"When listing jobs, wrap per-row mapping so one corrupt row degrades to a skipped entry with a warning instead of failing the entire cron list operation.","preventionTips":["Never hand-edit cron.db while the core is running; use the RPC/CLI surface","Keep workspace DB backups before migrations","Checkpoint/stop-the-world before copying DB files for backup or sync"],"tags":["cron","store","data-corruption","sqlite"],"backgroundTag":"corrupt-db-record","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}