Conversation
Replace stale read + full-row upsert with a scoped partial UPDATE inside a transaction: CAS guards on the enabled/schedule basis, typed conflicts for contested edits, Option.none on delete (never resurrects), merged project/thread binding validated against the v2 projection inside the same transaction, and BUSY-family contention retries on every run-state write. Web Settings saves through a new scheduledTasks.update WS RPC with a dirty-patch builder; a bound task project move is explicit unbind-and-move (threadId: null) with a detach hint on the Project field. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Archiving or deleting a thread pauses its bound schedules while keeping run history. Re-enabling is explicit: an enablement watermark (enabled_seq, migration 56) keeps a delayed archive event from undoing a later re-enable, and a run accepted before the archive keeps its recorded outcome. A domain-event reactor pauses promptly, a startup sweep covers archives committed while the server was down, and dispatch re-checks the binding inside the run-claim transaction. MCP schedule mutations now require a live caller-owned run and mode coverage for the modes the task will execute under. The authorizing run, caller modes and destination modes are pinned into the write transaction; restricted callers keep disable, rename and delete access. Archived caller threads are rejected. Ported onto the upstream OV2 branch from the original commits 64ebc96656, 9a8864c1c5, 887a2cbd56, b13acde17c, 56227a7fca, f289c404b8, 55ffa82883, 3890bfa8c5 and c525f94b3c. Project scoping for delete and runNow uses the `projectId` input from pingdotgg#11592 instead of a separate `expectedProjectId`; the Settings dirty-field patch work (bdec0f85e8) now lives in pingdotgg#11592. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This is a large, cross-cutting scheduled-task capability that changes production scheduling, persistence, thread lifecycle behavior, MCP authorization, and RPC handling, including a sensitive authorization-package change and database migrations. The scope and runtime impact exceed automatic-approval criteria. Notes:
You can add or adjust custom eligibility rules. Learn more. |
…t dispatch Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
| // Earlier tasks in this batch can take a while: judge each task's | ||
| // window against the moment it is about to dispatch, not the poll. | ||
| const current = yield* localNow; | ||
| return yield* isMissedFixedTimeRun(task.schedule, dueAt, current) || |
There was a problem hiding this comment.
This changes service dispatch behavior, but the focused tests only exercise the restriction helper; none verifies that a task due in-window is skipped when an earlier task delays dispatch past the window. Could you add a service-level test with a test clock and test layers that advances time during the earlier dispatch, then asserts the later task is rescheduled without launching?
Posted via Macroscope — Effect Service Conventions
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
| // never compacted for non-legacy commands — an accepted thread.archive | ||
| // receipt's result_sequence is the archived event's sequence — so the | ||
| // watermark unions both sources. Pin the partial archive index so the | ||
| // event lookup never walks the thread's unrelated event history. |
There was a problem hiding this comment.
This log annotation includes every paused task ID, so an archive with many bound tasks produces an unbounded log field. Could you log the count instead?
| // event lookup never walks the thread's unrelated event history. | |
| pausedTaskCount: paused.length, |
Posted via Macroscope — Effect Service Conventions
| if (paused.length === 0) return; | ||
| yield* Effect.logInfo("Paused schedule tasks bound to a thread that no longer accepts runs", { | ||
| threadId, | ||
| taskIds: paused.map((row) => row.task_id), |
This comment was marked as duplicate.
This comment was marked as duplicate.
Sorry, something went wrong.
|
Note This comment is posted by Julius' dot Closing this PR under the changed-behavior and UI verification requirement, assessed at 7eb5427. The current contribution changes the web and mobile schedule editors, restriction-only saves, and the visible Run now/Resume behavior once a lifetime cap is reached. I inspected the linked historical GIFs: they show the earlier web editor and a saved restriction summary, but no mobile UI or cap-reached → change-cap → explicit-resume interaction. The description explicitly says they predate this integration and that current web/mobile UI proof is still missing. The focused service/client tests are useful. Current CI logs confirm the scheduling service, schedule calculation, mobile draft, and web draft suites passed; those checks do not demonstrate the actual client interactions. The stacked prerequisites in #11592 and #11635 explain why the cumulative comparison includes atomic-update and archive-pause work, but do not supply the missing client evidence. For reconsideration, attach or link current before/after screenshots for the changed web and mobile controls, plus a short recording showing a restriction-only save surviving reopening and the run-cap pause/resume behavior, including the disabled Run now/Resume state and explicit re-enable after changing the cap. Identify the tested commit, environment, and observed results, and state any remaining platform limits. Keep PR-only media in the PR rather than committing it. See closure and reconsideration. |
|
Requesting reconsideration of the closure under Provide evidence for the changed behavior. I can't reopen a maintainer-closed PR myself ( The missing proof is now in the description. All captures are real, with disposable isolated fixtures:
Head updated (fast-forward): Checks
Scope. This is a focused configuration option for the existing scheduled-task capability. With no restriction set, scheduling is unchanged. No prior approval is linked; if you consider the limit's pause/resume semantics a product decision, I'll open an Ideas discussion. The PR still lands after the open #11592 and #11635. To see only this contribution, use Not checked: Android, desktop (Electron) and remote/relay connections. |
What Changed
Scheduled tasks gain three optional restrictions:
Example: every 30 minutes, Monday to Friday, 09:00–17:00, at most 16 runs.
Behavior:
schedule_task/update_scheduled_tasktools.In both editors, the Enabled switch shows the live task's state until the user changes it. It stays off and locked while the run limit of the schedule being saved is used up. A save sends
enabledonly after an explicit switch change. The last commit (3c9a91e024) adds this; preparing the UI proof showed that without it, a capped task's switch could appear on while the server kept the task paused.Why this is a focused configuration option
nextScheduledRunAtreturns the samefrom + everyMsas before, the fixed-time path is unchanged, and the run-limit check is false.Schedule.test.tscovers this: "keeps unrestricted intervals on their cadence" and "treats an empty weekday mask as every day".No prior Ideas discussion or maintainer approval is linked. If you consider the limit's pause/resume semantics a product choice that needs direction first, I'll open a discussion.
One problem. This PR lets users bound when and how often an existing scheduled task runs. The contract, server, web, mobile, MCP, test and user-guide changes are parts of that one problem, and so is the Enabled-switch change, which keeps the limit's paused state truthful. If you prefer, the run limit could be split from the day/hour restrictions.
Stack. This lands after two separate one-problem PRs that are still open: #11592 (atomic scheduled-task edits, head
d2c7ba7fd9) → #11635 (pause tasks bound to archived threads, heada3821de1da) → this PR. The comparison against the base therefore also shows their changes. To review only this contribution, usea3821de1da...3c9a91e024(19 files).UI Changes
All captures use disposable, isolated T3 homes and a fixture git project ("Nightly reports"), with no real user data.
0fb3731eb4. The scheduled-task client and server files are identical at the current targetb729550de6.3c9a91e024.Before (base
0fb3731)Interval tasks offer only "run every N minutes", and the row has no run-limit state.
web MP4
mobile MP4. This base mobile recording predates T3's Device panel on this machine: the Simulator was driven with AXe and recorded with
simctl. The UI it shows is the real base build.After: a restriction-only save survives reopening
Mobile check: the server stored
{"type":"interval","everyMs":900000,"weekdays":[1,2,3,4,5],"window":{"start":"09:00","end":"17:00"},"maxRuns":2}. The interval and prompt were unchanged by the edit.web MP4
mobile MP4
After: the limit pauses the task, and resuming takes an explicit action
Mobile:
enabled=false).enabled = 0).enabled = 1).Web: the same task is shown in the overnight observation below, auto-paused at "Run limit reached (3/3)":
mobile MP4
web MP4
Stills and an overnight scheduler observation
Capture notes
3c9a91e024, driven through T3's Device panel/AgentDevice, at 1206×2622 in real time.Verification
Focused checks only; no repo-wide checks were run.
7eb5427(previous head)3c9a91e0243c9a91e024cherry-picked on7eb5427+ target23309198bascheduledTask.test.ts7eb5427+ target)7eb5427+ target)Mergeability: the branch merges cleanly into the current target
b729550de6(git merge-tree). None of the target's 27 commits since23309198batouch scheduled-task client, server, contract or MCP schedule code. The test suites above were not re-run againstb729550de6itself.Compatibility limits:
falseis honored, but mixed-version preservation is not claimed.Checklist
Attribution:
🤖 Generated with Claude Code