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>
…t dispatch Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…eave it Editing a task that had used up its run cap still let the Enabled switch turn on; Save reported success, but the server keeps capped tasks paused, so reopening showed it off again. The switch could also show a stale value when the task paused, or another client changed it, while the editor was open. The web and mobile editors now show the live task's enabled state until the user uses the switch, keep it off and locked while the cap of the schedule the save will keep is used up, and send enabled only after an explicit switch action. Raising or clearing the cap therefore keeps a paused task paused until the user turns Enabled on, and a pause made elsewhere is never undone implicitly. Mobile also treats an explicit Enabled change as unsaved work when deciding whether to warn before discarding the editor. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
| aria-label="Window start" | ||
| className="w-32" | ||
| disabled={!draft.windowEnabled} | ||
| value={draft.windowStart} |
There was a problem hiding this comment.
🟡 Medium settings/ScheduledTasksSettings.tsx:1006
Opening a task with a persisted window time such as 9:00 renders the native time input blank, so users cannot see the configured restriction even though it remains active. The bindings pass draft.windowStart and draft.windowEnd directly to type="time", which requires padded HH:mm values; normalize both values before binding them.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/settings/ScheduledTasksSettings.tsx around line 1006:
Opening a task with a persisted window time such as `9:00` renders the native time input blank, so users cannot see the configured restriction even though it remains active. The bindings pass `draft.windowStart` and `draft.windowEnd` directly to `type="time"`, which requires padded `HH:mm` values; normalize both values before binding them.
There was a problem hiding this comment.
Confirmed and fixed in 01a7974: the contract accepts single-digit hours such as 9:00, and the web editor passed them unchanged to the native time inputs. taskToDraft now pads window start/end to HH:mm, and a regression test covers it. A real-input check on the fixed head shows the inputs reading 09:00/17:00 and displaying 09:00 am / 05:00 pm. Padding happens symmetrically on the editor baseline, so an unchanged save still sends no schedule patch.
There was a problem hiding this comment.
Sorry, I'm unable to act on this request because you do not have permissions within this repository.
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This is a substantial, cross-cutting scheduler feature that changes production scheduling, persistence, orchestration, authorization, and archive lifecycle behavior across server, web, and mobile code. New migrations and auth/dispatch gates, plus complex DST and concurrency handling, make the blast radius too broad for automatic approval. Not approved because:
Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more. |
…back, show unpadded times Two Macroscope findings on the scheduled-task restrictions: - In a repeated fall-back hour, the restricted interval schedule added the interval with calendar arithmetic, which re-resolves the repeated hour to its first occurrence. The next run could land before the current time, so a window opening inside that hour kept the task due and rescheduled on every poll. The restricted path now adds elapsed time, and a window opening that resolves before the candidate steps forward on the candidate's own offset. - The contract accepts window times such as "9:00", but the web editor passed them unchanged to native time inputs, which only display zero-padded HH:mm, so the configured window showed as blank. The editor now pads them when it reads a task. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…indow in a fall-back hour A time window that overlaps a fall-back hour is entered a second time when the hour repeats. After the first pass through the window ended, the restricted interval schedule skipped straight to the next day, although the repeated part of the window was still ahead and the dispatch check would allow a run in it. The schedule now uses the earliest re-entry later the same day: the repeated window opening, or the fall-back instant itself when the window began before the repeated hour (for example 00:30-01:30 in New York, or Lord Howe's 30-minute fall-back). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Note This comment is posted by Julius' dot Closing under UI verification for the current cumulative diff. The new web/iOS recordings establish restriction-only saves and run-cap pause/resume. However, this 48-file comparison also includes the mobile archived-thread banner/Unarchive control and web archive notice from #11635. None of the current captures or recordings shows those changed surfaces or archive → pause → unarchive → explicit re-enable; the supplied flows stay in the scheduled-task editors. Please add before/after captures and a short recording for those cumulative archive interactions, or rebase after the prerequisites land so they leave this diff, then request reconsideration. The repaired restriction/cap evidence remains useful. |
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...47ff79839e(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 targetde95adc336.3c9a91e024. The two later commits don't change the captured flows; see Review follow-ups.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.Review follow-ups since
3c9a91e024These came from Macroscope's review and independent reviews. All are in this PR's restriction layer; the base has no time windows.
01a7974c83fixes two issues:9:00showed as a blank time field on web (the contract allows single-digit hours). The editor now pads it. Real-input check on this commit: the native inputs read09:00/17:00and display 09:00 am / 05:00 pm (still; disclosed fixture write while the server was stopped).47ff79839e: a window that overlaps a fall-back hour now runs again when the clock re-enters it later the same day, instead of skipping to the next day. This covers windows that start inside the repeated hour and windows that start before it, including half-hour shifts.Why the media above is still valid: the captures ran at
3c9a91e024, and none of the captured flows involve a DST transition or an unpadded stored time. The later commits change only those cases, so the UI shown is unchanged. They are covered by focused tests plus the still above.Left unchanged:
timeOfDayvalues like9:00aren't padded on web.Verification
Focused checks only; no repo-wide checks were run.
7eb5427(#14354 head)3c9a91e02447ff79839e(this head)3c9a91e024(51)scheduledTask.test.tsEarlier, the suites also passed with
3c9a91e024cherry-picked onto7eb5427merged with target23309198ba: web 59, mobile 51, contracts 9, server 120.Independent reviews of the follow-ups (GPT-6.1 Sol, high reasoning, Codex harness through T3 Code): neither
01a7974c83nor47ff79839eintroduced any actionable issue. The last review compared 224,400 schedule cases against the earliest permitted run and found no fall-back mismatches. The only mismatches were the documented spring-forward limitation.Mergeability: this head (
47ff79839e) merges cleanly into the current targetde95adc336(git merge-tree). None of the target's 28 commits since23309198batouch scheduled-task client, server, contract or MCP schedule code. The target's migrations stop at 056, so the stack's 057/058 don't collide. The test suites above were not re-run againstde95adc336itself: its lockfile changed, and reinstalling was deferred for disk reasons.Compatibility limits:
falseis honored, but mixed-version preservation is not claimed.Checklist
Attribution:
🤖 Generated with Claude Code