You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Scheduled tasks: skip a run with a gate command and pass its output to the prompt
#33242
apps/server (scheduled tasks), contracts, the scheduled task editor on web and mobile, and the schedule_task MCP tool.
Problem or use case
Every time-based scheduled run starts an agent turn, even when there is nothing to do. To skip one today, the prompt has to say "if nothing changed, stop". That still pays for a turn and leaves a no-op turn in the thread.
Many checks are cheap for a script and expensive for a model: a new comment on a discussion, a red CI run in another repository, new rows in a queue, a time-of-day window (#26121), free memory (#32205). #19879 puts a number on it. Polling a discussion every 30 minutes for a week is about 340 turns to learn about one comment.
Webhook tasks already have both pieces this needs. filters skip a delivery without a turn, logged as filtered, and {{body.path}} templates put request data into the prompt. Interval, fixed-time and one-time tasks have neither.
Proposed solution
An optional gate command on interval, fixed-time and one-time tasks. When the task is due, the server runs it on the task's environment before it starts the turn.
Gate result
Run
exit 0
Starts. Stdout goes into the prompt.
exit 1, empty stderr
Skipped. No thread, no turn.
any other exit, or timeout
Skipped, recorded as a gate failure with the stderr tail.
Details, checked against ScheduledTaskService.runTask:
Next run. After a run the service computes nextRunAt from the stored schedule. A skip goes through the same path, so waiting for the next execution needs no new scheduling logic.
Context. Stdout is available as {{gate}} in the prompt through the webhook template renderer, and is appended in a labelled block when the prompt doesn't use it. Capped at something like 16 KiB.
Working directory. The project root for tasks that launch a new thread, since a worktree workspace is created at launch, after the gate. The bound thread's workspace for continue and fresh_session tasks.
Recording skips.lastRunStatus is a closed never | running | succeeded | failed list, and clients decode the task list with ForwardCompatibleArray, so a new status value would make the task disappear on shipped clients. A skip leaves lastRunStatus and runCount alone and writes new optional fields such as lastGateAt, lastGateResult: "skipped" | "failed" and lastGateError. The task list shows the last gate result, so a gate that never passes is visible.
Run now runs the gate too, so it doubles as a test.
Example: a task that summarizes new comments on a discussion.
A gh error exits 2 and shows up as a gate failure. No new comments exits 1 and skips quietly.
Permissions
Saving a task already requires orchestration:operate, and a full-access task already runs agent commands with no approval prompt. Accepting a gate only on full-access tasks adds no new capability. The server rechecks the mode before each gate run. Managed policy gets a scheduledTaskGates switch next to webhookTasks. Limits: a 60 second timeout and a cap on concurrent gate runs per environment.
Alternatives considered
watch_command (Wake a thread when a command prints something (`watch_command`) #19879). Thread-owned, with a Waiting state and its own lifecycle. A gate reuses the scheduler, editor and cadence that exist, and covers tasks that start a new thread each run. The two fit together: a watch tells a thread when something happens, a gate runs a job only when there is work.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Note
🤖 Claude Opus 5.5 responding on behalf of Guria
Before submitting
Area
apps/server (scheduled tasks), contracts, the scheduled task editor on web and mobile, and the
schedule_taskMCP tool.Problem or use case
Every time-based scheduled run starts an agent turn, even when there is nothing to do. To skip one today, the prompt has to say "if nothing changed, stop". That still pays for a turn and leaves a no-op turn in the thread.
Many checks are cheap for a script and expensive for a model: a new comment on a discussion, a red CI run in another repository, new rows in a queue, a time-of-day window (#26121), free memory (#32205). #19879 puts a number on it. Polling a discussion every 30 minutes for a week is about 340 turns to learn about one comment.
Webhook tasks already have both pieces this needs.
filtersskip a delivery without a turn, logged asfiltered, and{{body.path}}templates put request data into the prompt. Interval, fixed-time and one-time tasks have neither.Proposed solution
An optional
gatecommand on interval, fixed-time and one-time tasks. When the task is due, the server runs it on the task's environment before it starts the turn.Details, checked against
ScheduledTaskService.runTask:nextRunAtfrom the stored schedule. A skip goes through the same path, so waiting for the next execution needs no new scheduling logic.{{gate}}in the prompt through the webhook template renderer, and is appended in a labelled block when the prompt doesn't use it. Capped at something like 16 KiB.continueandfresh_sessiontasks.lastRunStatusis a closednever | running | succeeded | failedlist, and clients decode the task list withForwardCompatibleArray, so a new status value would make the task disappear on shipped clients. A skip leaveslastRunStatusandrunCountalone and writes new optional fields such aslastGateAt,lastGateResult: "skipped" | "failed"andlastGateError. The task list shows the last gate result, so a gate that never passes is visible.Example: a task that summarizes new comments on a discussion.
A
gherror exits 2 and shows up as a gate failure. No new comments exits 1 and skips quietly.Permissions
Saving a task already requires
orchestration:operate, and afull-accesstask already runs agent commands with no approval prompt. Accepting a gate only onfull-accesstasks adds no new capability. The server rechecks the mode before each gate run. Managed policy gets ascheduledTaskGatesswitch next towebhookTasks. Limits: a 60 second timeout and a cap on concurrent gate runs per environment.Alternatives considered
watch_command(Wake a thread when a command prints something (`watch_command`) #19879). Thread-owned, with a Waiting state and its own lifecycle. A gate reuses the scheduler, editor and cadence that exist, and covers tasks that start a new thread each run. The two fit together: a watch tells a thread when something happens, a gate runs a job only when there is work.[ "$(date +%H)" -ge 8 ] && [ "$(date +%H)" -lt 19 ]is a valid gate, but a window is easier to set in the editor, so both can exist.Related
#20258, #19879, #26121, #32205, and #31997, where a gate would belong in the file-based task config.
All reactions