#13288: fix(cron): normalize string schedule/payload from non-frontier LLMs
stale
Cluster:
Cron Enhancements and Fixes
## Summary
- Fix `normalizeCronJobInput` to handle `schedule` and `payload` passed as plain strings instead of objects
- Non-frontier LLMs (Qwen 3 Coder, Grok) send these as strings, which `isRecord()` silently drops, causing gateway validation to fail with "must have required property" for all four required fields
- String schedule is parsed as a cron expression (e.g. `"0 5 * * *"`) or ISO timestamp (e.g. `"2026-03-01T10:00:00Z"`)
- String payload is wrapped as `{ kind: "systemEvent", text: payload }`
Closes #9283 — thanks @Displayer226 for the detailed report!
## Test plan
- [x] String schedule as cron expression: `schedule: "0 5 * * *"`
- [x] String schedule as ISO timestamp: `schedule: "2026-03-01T10:00:00Z"`
- [x] String payload: `payload: "hello world"`
- [x] Combined: string schedule + string payload in a single job object
- [x] All 258 existing tests still pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
Updates cron job normalization to accept non-frontier LLM outputs where `schedule` and/or `payload` are sent as plain strings. String schedules are interpreted as either a cron expression (5–6 fields) or an absolute timestamp (ISO/epoch), and string payloads are wrapped into a `systemEvent`. The test suite is extended with cases covering string schedule (cron + ISO), string payload, and combined inputs.
Key behavior to double-check before merge: (1) empty/whitespace string `payload` now prevents the existing fallback that constructs payload from top-level `message`/`text`, and (2) unparseable non-empty string `schedule` is currently left as a string in the normalized output (since `next` starts as `{...base}`), which will still fail downstream validation in a less consistent way than prior behavior.
<h3>Confidence Score: 4/5</h3>
- Mostly safe to merge after addressing two normalization edge cases.
- The change is localized to cron normalization and is covered by new tests for the main reported scenarios, but there are two concrete input patterns (empty string payload with message/text present; non-empty but unparseable string schedule) that will now produce invalid normalized output and likely break validation.
- src/cron/normalize.ts
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#9670: fix: handle numeric string timestamps in cron schedule normalizatio...
by divol89 · 2026-02-05
78.7%
#18743: Cron Tool Hardening: Normalize Gateway Params and Enforce Valid Sch...
by cccat6 · 2026-02-17
78.2%
#15875: fix(cron): normalize seconds to milliseconds in timestamps
by Shuai-DaiDai · 2026-02-14
77.8%
#12303: fix(cron): correct nextRunAtMs calculation and prevent timer stall
by colddonkey · 2026-02-09
76.9%
#8811: fix(cron): handle atMs fallback for kind=at jobs
by hlibr · 2026-02-04
75.8%
#18856: fix(cron): reject past timestamps in createJob (defense-in-depth)
by kevinodell · 2026-02-17
75.5%
#8698: fix(cron): default enabled to true for new jobs
by emmick4 · 2026-02-04
75.5%
#13065: fix(cron): Fix "every" schedule not re-arming after gateway restart
by trevorgordon981 · 2026-02-10
75.5%
#2071: fix: accept JSON string for cron.add job parameter (#1940)
by andrescardonas7 · 2026-01-26
75.3%
#15754: fix: handle Unix timestamps in seconds in parseAbsoluteTimeMs
by MisterGuy420 · 2026-02-13
74.9%