Description
All cron jobs created with every_seconds or cron_expr are silently stored as
one-time schedule.kind: "at" tasks with deleteAfterRun: true. They execute
once and are then deleted.
Steps to Reproduce
- Ask PicoClaw to create a recurring task (e.g., "remind me every 2 hours to drink water")
- Inspect
~/.picoclaw/workspace/cron/jobs.json
- Observe:
schedule.kind is "at", deleteAfterRun is true
Root Cause
In pkg/tools/cron.go, the addJob function uses Go type assertions:
atSeconds, hasAt := args["at_seconds"].(float64)
LLMs frequently fill unused optional parameters with default values (e.g.,
at_seconds: 0). Go's type assertion returns (0, true) for zero values, so
hasAt is always true. Since at_seconds has the highest priority in the
if-else chain, it always wins — turning all recurring tasks into one-time tasks
that run immediately (0 seconds from now).
Proposed Fix
Add value validity checks after type assertions (3 lines):
hasAt = hasAt && atSeconds > 0
hasEvery = hasEvery && everySeconds > 0
hasCron = hasCron && cronExpr != ""
This has zero risk of regression — legitimate at_seconds > 0 values continue
to work exactly as before.
Environment
Description
All cron jobs created with
every_secondsorcron_exprare silently stored asone-time
schedule.kind: "at"tasks withdeleteAfterRun: true. They executeonce and are then deleted.
Steps to Reproduce
~/.picoclaw/workspace/cron/jobs.jsonschedule.kindis"at",deleteAfterRunistrueRoot Cause
In
pkg/tools/cron.go, theaddJobfunction uses Go type assertions:LLMs frequently fill unused optional parameters with default values (e.g.,
at_seconds: 0). Go's type assertion returns(0, true)for zero values, sohasAtis alwaystrue. Sinceat_secondshas the highest priority in theif-else chain, it always wins — turning all recurring tasks into one-time tasks
that run immediately (0 seconds from now).
Proposed Fix
Add value validity checks after type assertions (3 lines):
This has zero risk of regression — legitimate
at_seconds > 0values continueto work exactly as before.
Environment