Repository navigation
Description
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/web
Steps to reproduce
My use case, because I think it explains why this matters: I run an orchestrator-style workflow in a project. A top-level chat delegates to large numbers of subagents across several models, and the orchestrator skill keeps bookkeeping in .orchestrator/: per-model notes with facts about each subagent run (task type, outcome, rework, failures). To make sure that daily bookkeeping actually happens, I created a scheduled T3 Code task: every morning at 08:00 it reviews yesterday's git activity and subagent runs and updates the model notes. The prompt is self-contained and the task runs on a paid model (mimo-v2.6-flash) on an unattended machine. Unattended is the point — the status line is the only signal that it worked.
Reproduction (T3 Code server v0.1.5 / opencode 2.2.5):
- Create a scheduled task with
schedule: {type:"fixed_time", timeOfDay:"08:00"}, bound to a runner thread, running a paid model whose plan quota can be exhausted. - Reach the provider's usage limit (e.g. weekly quota) and let the schedule fire.
- For the second bug: on several days, have the machine off at the fixed-time slot.
Expected behavior
lastRunStatus/lastRunErrorshould reflect the terminal state of the run in the bound thread (failed, interrupted, cancelled), not just "the prompt was posted and a run was created".- The user should be notified (or the thread flagged/readable as failed) when an automated scheduled run fails, especially when the agent produced neither a report nor file changes.
- Missed fixed-time slots should at least be visible: either catch up on boot (optionally with a max age, like a
catchUpWindow), or surface "skipped N missed slots" in the task status.
Actual behavior
- The thread run failed in ~1 second with the provider error (
OpenCode Go weekly usage limit reached), butlist_scheduled_tasksshowedlastRunStatus: "succeeded"andlast_run_error: null. - Independent confirmation: the scheduler's own record (
last_run_status: "succeeded",last_run_error: null) contradicted the bound thread's run record for that minute, which readsstatus: "failed"with the provider error. - On the second bug: after several days with the machine off at 08:00, no run happened for any missed day;
nextRunAthad already advanced to the following day andlastRunStatuskept the last OK value from an earlier manual run. - Net effect: the task looked green across a whole week while the automation's target files had their last update a week earlier and zero failure notifications arrived. Unattended automations are the one place where the status line is the only signal, so a silent no-op agent looks identical to a silent-but-healthy one; the breakage was only detectable by manually diffing the output files.
Impact
Major degradation or frequent failure
Version or commit
No response
Environment
- opencode version: 2.2.5 (T3 Code server reports serverVersion 0.1.5) - OS: Linux x64, Ubuntu, kernel 7.0.0-42-generic - Shell: /bin/bash - Scheduled task runs headless / server-side (no terminal involved) - Active plugins: none
Logs or stack traces
Screenshots, recordings, or supporting files
No response
Workaround
What keeps it alive today (unfixed semantics, reduced blast radius):
- Replaced
fixed_timewithintervalscheduling (every 20h): a missed slot fires on the next boot if the machine was off, so downtime no longer silently skips days. - Prompt made self-healing: each run backfills every missing day since the newest dated entry (not just yesterday), and must grep-verify its own writes before reporting. Verified this in practice: after a week of missed days, the first successful run backfilled all seven days correctly.
- For the quota variant specifically: Extra Usage on the OpenCode Go plan removes the recurring weekly failure.
Possible fixes in the product: write the run's terminal status into lastRunStatus (a posted prompt should map to running, not succeeded); notify the user on failed scheduled runs; optionally expose the last executed runId next to the status for cross-checking; consider an optional catch-up window for missed fixed-time slots.
Activity
chrisfonte commentedon Nov 5, 2026
I found a head-of-line path that can explain a missed fixed-time task even while the server process stays up. At 0.1.6-nightly.20261030.3940 (ae521d1), Scheduler.ts uses one permit per source and ScheduledTaskService.runDueTasks awaits due task dispatches sequentially, without a dispatch timeout. One hung launch/send therefore delays the remaining due tasks. Tasks already in that due batch use tick-start time and run late; tasks becoming due during the hang can be skipped on the next tick. When dispatch resumes, a fixed_time task more than ten minutes late is skipped and advanced to its next occurrence; its prompt and self-disable never run.
In one deployment, a bound dispatch remained outstanding through a filesystem outage. Another fixed-time task became due within that span and subsequently showed the following week's slot with no corresponding scheduled message. The exact blocking call and skip log were unavailable after trace rotation, so this particular skip explanation is inferred from source and the observed timeline, not a captured skip-log proof. This is separate from an on-time queued message whose provider turn starts much later.
It may help to expose dispatch versus provider-start/completion separately, make missed-slot skips visible in task history, and bound dispatch waits so one blocked task cannot delay unrelated due tasks. Native once in v0.1.7 runs late after downtime, but does not remove the sequential-dispatch concern.
Source:
t3code/apps/server/src/scheduledTasks/ScheduledTaskService.ts
Lines 1038 to 1050 in ae521d1
t3code/apps/server/src/scheduledTasks/Schedule.ts
Lines 64 to 73 in ae521d1
fix(server): a scheduled run the provider fails afterwards shows as f…
fix(server): a missed fixed-time slot cannot overwrite a newer run
fix(server): a stopped or cancelled scheduled run no longer reads as …