Summary
The automation scheduler's once-per-day guard is in-memory only and is set after the complete automation finishes. This permits duplicate execution both after an app restart and when an execution lasts longer than the one-minute scheduler interval.
Evidence
tickAutomations() runs every 60 seconds in apps/electron/src/main/kernelHost.ts.
runDue() returns an enabled scheduled rule for the entire remainder of its scheduled day once its scheduled time has passed.
- The only dedupe state is
lastAutomationRunByRule: Map<string, string>; it is constructed empty when AgentKernelHost is created.
- The map is updated only after both
executeAutomation(rule) and automationRuns.record(run) resolve. No persisted AutomationRun is consulted before starting a job, and there is no in-flight lock.
Reproduction
Restart path
- Enable a daily automation and let it finish after its scheduled time.
- Restart Folio before local midnight.
- Wait for the next scheduler tick (up to 60 seconds).
The new host has an empty map, while runDue() still considers the rule due. The same daily automation runs again, even though its earlier run is present in automation-runs.json.
Overlap path
- Enable a scheduled automation whose execution takes longer than 60 seconds (a material-change path can start research).
- Let the first scheduled tick enter
executeAutomation().
- Before it resolves, let the next interval tick fire.
Because the map is not populated until the first run completes, both ticks pass the guard and can execute the same rule concurrently. This can duplicate research, notifications, and provider calls.
Expected
At most one scheduled run per rule and scheduled occurrence, across restarts and overlapping ticks. Failed-run retry behavior should be explicit and bounded rather than an accidental one-minute re-run.
Suggested direction
Persist a per-occurrence state/claim before executing (or atomically derive it from persisted run records), recover it at startup, and add an in-flight per-rule guard. Add tests covering:
- successful execution followed by a same-day host recreation;
- an execution held past the next tick;
- a failure/retry policy that does not create duplicates.
Summary
The automation scheduler's once-per-day guard is in-memory only and is set after the complete automation finishes. This permits duplicate execution both after an app restart and when an execution lasts longer than the one-minute scheduler interval.
Evidence
tickAutomations()runs every 60 seconds inapps/electron/src/main/kernelHost.ts.runDue()returns an enabled scheduled rule for the entire remainder of its scheduled day once its scheduled time has passed.lastAutomationRunByRule: Map<string, string>; it is constructed empty whenAgentKernelHostis created.executeAutomation(rule)andautomationRuns.record(run)resolve. No persistedAutomationRunis consulted before starting a job, and there is no in-flight lock.Reproduction
Restart path
The new host has an empty map, while
runDue()still considers the rule due. The same daily automation runs again, even though its earlier run is present inautomation-runs.json.Overlap path
executeAutomation().Because the map is not populated until the first run completes, both ticks pass the guard and can execute the same rule concurrently. This can duplicate research, notifications, and provider calls.
Expected
At most one scheduled run per rule and scheduled occurrence, across restarts and overlapping ticks. Failed-run retry behavior should be explicit and bounded rather than an accidental one-minute re-run.
Suggested direction
Persist a per-occurrence state/claim before executing (or atomically derive it from persisted run records), recover it at startup, and add an in-flight per-rule guard. Add tests covering: