Add scheduled recurring agent runs - #386
Open
0x92 wants to merge 6 commits into
Open
Conversation
start() called rescheduleAll(), which recomputed every enabled schedule's next run from now. A stored time in the past was therefore replaced with a future one before the first tick ever looked at it, so the tick had nothing overdue to find: lastRunStatus never became 'skipped' and the history showed no sign that the run had been due at all. The same erasure took out runs that were meant to happen. A schedule two minutes late is inside SCHEDULE_MISS_GRACE_MS and should start; instead it was moved to the next occurrence and dropped. rescheduleAll also rewrote the next run of schedules that were not overdue in any sense, including one a user had just saved. reconcileOnStart() now decides only the two things startup can decide: a schedule with no next run at all gets one, and one that is late beyond the grace period is written off as missed. Everything else keeps its stored time and the tick decides, which is where that belongs. The missed-run bookkeeping tick() already did moves into recordMissed() so both paths write the same thing — one skipped entry per schedule, not one per interval of downtime, because computeNextRun counts from now. The existing tests drove tick() with a hand-built overdue row and never called start(), which is exactly where the bug lived.
parsakhaz
requested changes
Aug 23, 2026
parsakhaz
left a comment
Member
There was a problem hiding this comment.
Verdict: Request changes - four correctness defects violate the scheduled-run behavior stated in the PR.
Counts: Must Fix: 4 (security: 0) · Should Fix: 1 · pass 1/3
Must Fix
- MF-1 - Startup does not immediately process a run that is inside the 15-minute grace window · main/src/services/scheduleManager.ts:55-72 · run the first tick immediately after reconciliation · violates the stated missed-run grace behavior
- Evidence: start() only installs a 30-second interval. The startup test helper manually calls manager.tick() at main/src/services/scheduleManager.start.test.ts:63-68, so it does not exercise the actual launch sequence.
- Failure scenario: A run is 14m50s late when Pane opens. It is still eligible at reconciliation, but the first interval fires 30 seconds later and records it as skipped.
- MF-2 - In-flight execution is not coordinated with Run now, delete, disable, or edits · main/src/services/scheduleManager.ts:137-159 and main/src/services/scheduleManager.ts:209-240 · serialize by schedule id and merge execution results into the latest stored row · violates Run now starting one run and schedule mutation semantics
- Evidence: runNow() bypasses the tick-level running guard, while execute() awaits session creation and later upserts the stale object it loaded before the await.
- Failure scenario: A due tick and Run now can create two sessions. Deleting or disabling a schedule while its session is starting can later resurrect or re-enable it when execute() upserts its stale copy.
- MF-3 - The daemon boundary accepts malformed schedule variants that can never run · main/src/ipc/schedule.ts:18-34 · validate kind, toolType, integer interval/weekday, project id, and parsed clock time · violates the three supported schedule shapes
- Evidence: the regex accepts 99:99, unknown kind values fall through as weekly, and runtime IPC input is trusted as ScheduledRunInput. computeNextRun() then returns null for invalid clock values.
- Failure scenario: A remote or renderer caller saves an enabled schedule successfully, but it has no next run and silently never starts.
- MF-4 - The UI omits the promised link to the session created by the last successful run · frontend/src/components/schedule/ScheduledRunsDialog.tsx:167-172 · render lastSessionId as an action that closes the dialog and opens that session · violates the PR description under per-run state
- Evidence: the row prints only time, status, and error even though lastSessionId is returned and persisted.
- Failure scenario: A user sees a successful scheduled run but cannot navigate from the schedule to the session it created.
Should Fix
- SF-1 - Rebase residue adds unrelated and inconsistent declarations · frontend/src/types/electron.d.ts:39 and main/src/preload.ts:135 · remove the unused GitDiffResult import and unrelated pr: prefix
- Evidence: GitDiffResult has no references in electron.d.ts, and pr: is added only to the preload copy, not shared/types/daemon.ts.
Praise
- The pure schedule calculator cleanly covers DST and cadence arithmetic.
- SQLite uses bound parameters and a foreign-key cascade, with no injection issue found.
- The focused suite passes: 4 files, 54 tests.
parsakhaz
force-pushed
the
feature/scheduled-runs
branch
from
August 23, 2026 19:53
1184aa8 to
d14e987
Compare
parsakhaz
approved these changes
Aug 23, 2026
parsakhaz
left a comment
Member
There was a problem hiding this comment.
Verdict: Approve - all four Must-Fix findings are resolved on d14e987.
Counts: Must Fix: 0 (security: 0) · Should Fix: 0 · pass 2/3
Must Fix
- MF-1 fixed: startup performs an immediate tick, including the grace-boundary regression.
- MF-2 fixed: execution locks by schedule id and merges results into the latest stored row.
- MF-3 fixed: daemon input uses the shared boundary decoder plus domain validation.
- MF-4 fixed: the dialog opens the last created session, covered by Playwright.
Praise
- Root typecheck and full lint pass under Node 22.
- Six focused test files pass with 65 tests.
- The scheduled-run Playwright journey passes.
Member
Review, simplify, refactor completeRebased onto current main and pushed final head REVIEW
SIMPLIFY
REFACTOR
Verification
Follow-ups
Left for parsa
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Let a project start agent sessions on a schedule: a fixed prompt, at a fixed time, in a fresh worktree.
Pane already knows how to create a session from a prompt — this makes that repeatable without a person present. Nightly bug sweeps, a weekly dependency check, a "review yesterday's diffs" pass every morning: the work that is worth doing regularly but never worth remembering.
A new Scheduled runs entry in the project's menu manages them.
What it does
Every day at 03:00 — next run in 4h 12m). Cron is more expressive than anyone actually needs here and impossible to render as a sentence.claude, ornonefor a plain terminal) and an optional worktree/branch template.ON DELETE CASCADE).How it works
scheduleCalculator.tsis pure and holds every decision about when:computeNextRun,isDue,isMissed. Time zones, daylight saving and "the next weekly run when today already passed" are the parts that break silently, so they are unit-tested directly rather than through the scheduler.ScheduleManagerticks every 30s and asks the calculator; it talks to storage through a smallScheduleStoreinterface, so its behaviour is tested without a database.createSessionAndWait, solastSessionIdis the real session id rather than a queue job id — the difference only shows up when you click the link.scheduled_runs, plus an index on(enabled, next_run_at_ms)so the tick is a single indexed lookup.schedules:*): the schedule belongs to the machine that runs the agents.Type of Change
Checklist
pnpm typecheckandpnpm lintlocallypnpm electron-devCritical Areas Modified
Additional Notes
Schema note:
schema.sqlis split on the statement separator at startup, so a;inside a comment breaks the database. The new block is written accordingly.Tested in the running app, not only in unit tests: a schedule was created, fired on its own tick, produced a real session in a fresh worktree, recorded its outcome, and was then disabled and deleted. Missed-run handling was exercised by moving a schedule's due time into the past.
Automated QA
Status: Passed on
d14e9878with synthetic projectScheduled QA./tmp/pane-pr386-qa. macOS accessibility automation was unavailable, so renderer interaction used the repository Playwright Electron API mock.Remaining human check: allow one real scheduled run to fire in a fresh worktree with the preferred installed agent.