{{ message }}
Repository navigation
feat(core): cron tools for scheduled prompts - #44191
Closed
paolodelia99 wants to merge 37 commits into
Closed
paolodelia99 wants to merge 37 commits into
paolodelia99 wants to merge 37 commits into
Conversation
…each SessionPrompt
…s them Effect.catchCause ran before Effect.catch, so typed CronDeliveryError failures were caught by catchCause's non-interrupt branch and logged as defects. Swap the pipe order so catch handles typed errors first and catchCause only sees true defects.
Add regression tests for the sooner-job precedence fix (8ef9510), busy-port reschedule, exists=false rejection, non-defect delivery failure logging, parseDuration boundary (60s, zero-prefixed, all-zero), cron_add/list/delete round-trips, and legacy port exists/isBusy-true/ deliver-error-mapping branches.
…interval reschedule
…e to now+interval - add: set nextRunAt = now so the loop picks up the job immediately - busy branch: poll with 10s retry when runCount === 0 until session is free, then fire the first run and anchor the cadence - busy branch: reschedule to now + interval (not nextRunAt + interval) on subsequent busy ticks so the cadence re-anchors at the busy moment - post-delivery: set nextRunAt = current + interval to prevent drift
The core cron node (V2 port) and core tool/cron.ts were dead code. The opencode app defines its own cron tools via Tool.define and wires CronService with the legacy port. The server never exposes cron endpoints so these core definitions were never exercised.
Replace the isBusy-check-and-reschedule branch with service-level delivery coalescing. At each tick the loop advances nextRunAt by intervalMs (reverting the earlier current+interval change), re-inserts the job, and forks the delivery; the legacy runner queues a delivery behind a busy session via ensureRunning. A Ref<Map<jobId, Fiber>> tracks pending deliveries so at most one is queued per job, and remove() interrupts a pending delivery fiber. Removes BUSY_RETRY_MS.
A magnitude such as a 400-digit hour count overflows the millisecond computation to Infinity, which slips past the minimum-interval floor and reaches the scheduler as a sleep(Infinity). Reject non-finite results with a CronError.
Contributor
hunterchristian
added a commit
to chipp-ai/opencode
that referenced
this pull request
Sep 23, 2026
A monitor tool (plus monitor_list/monitor_stop) lets the model watch a long-running background command's output for a pattern, or until it exits, and get notified back into the session as new messages -- a standing watch, not just a one-shot check. Analogous to Claude Code's own Monitor tool. Gated behind OPENCODE_EXPERIMENTAL_MONITOR, matching the existing experimentalBackgroundSubagents convention. Ported from upstream anomalyco/opencode PR anomalyco#33806 "feat: add Monitor tool" (1,398 lines, closed by the automated PR-cleanup bot, never reviewed) -- built directly on this fork's existing BackgroundJob primitive rather than modifying it. Reused nearly word-for-word: the re-arm/replace parameter shape, session-scoped list/stop logic, the untrusted-output fence with an unpredictable id so watched output can't forge its own closing tag, control-character stripping, the never-grant-"always"-for-a-glob-command permission rule, the detached-process-plus-parent-death-watchdog design, the flood guard, and most of the tool description and test scenarios. Rewritten rather than ported: avoided anomalyco#33806's own change to core BackgroundJob (a WakeScope to dodge a self-re-arm deadlock) by forking notifications into the tool's own scope instead, the same pattern task.ts already uses; its hand-written reader/debounce became an Effect Stream pipeline; its base64 command-wrapper isn't needed since this repo's bash tool already runs commands through plain ChildProcess.make; permission goes through the existing bash permission rather than a new one, so existing bash rules govern monitors too. Also considered anomalyco#44191 (cron scheduler, closed by the same bot the same week, praised by a human reviewer first) and the anomalyco#47166/anomalyco#47193/anomalyco#47284 durable-heartbeat resubmits (each closed within hours by a different, 2-hour PR-template-compliance bot, never reviewed) -- neither ported; see known gaps. Found and fixed a real, generalizable bug along the way: a command that writes output continuously with no pause (e.g. `yes`) hung teardown and starved timers, because the stdout read pipeline never yielded control back to the scheduler while data kept arriving. Fixed with a single Stream.tap(() => Effect.yieldNow), documented inline -- the same issue could affect other handle.stdout readers facing a non-pausing producer. Known gaps (deliberately scoped): not durable, in-memory only; no scheduler (only a shell poll-loop inside the watched command); opencode run's non-interactive mode exits once idle, killing active monitors; no UI (model-only via monitor_list); stdout only, 4000-char line cap, 5000-line flood guard; parent-death watchdog is POSIX-only; no external/webhook delivery. New packages/opencode/src/tool/monitor.ts (~360 lines) + monitor.txt. New packages/opencode/test/tool/monitor.test.ts (13 tests: self-cancel re-arm without deadlock, concurrency, list/stop, byte-flood guard, multi-line commands) -- passes 3 runs in a row. Verified against this fork's current dev (not just the agent's own stale worktree base): bun typecheck clean; full packages/opencode suite 3600 pass, only the 4 pre-existing, confirmed-unrelated cf-ai-gateway failures. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

Issue for this PR
Partially Closes #41907, #18001
Type of change
What does this PR do?
This PR adds support for scheduled prompts, a feature missing in opencode.
Inspired by Claude code’s /loop, here’s my attempt to bring scheduled prompts to opencode (under the name of cron), a feature that in some cases might be useful to developers. This is a first PR that exposes the scheduling of CronJobs through the brand-new
cron_add,cron_list,cron_deletetools; the usage of those tools is exposed (like in Claude code) through a built-in skills (which is scheduling.md). Those tools then interact with theCronService, which, as the name suggests, handles various CronJobs for various sessions and stores them in a PriorityQueue.The
CronService, in turn, uses theCronDeliveryPortto dispatch those CronJobs to the current prompt session.Please note that the
CronServicehas been thought to be used in the TUI and in the opencode app; I didn’t want to expose it to the SDK, since I don’t see any usage of that through the SDK.How did you verify your code works?
You can directly try to use the cron_add tool from the TUI like using:
or with more natural language:
Other examples:
Screenshots / recordings
No, UI changes.
Checklist