Desktop automations ignore timezone for RRULE scheduling / next execution
Summary
Codex Desktop automations appear to display a weekly RRULE as a local wall-clock time, but the computed next execution behaves as if BYHOUR is interpreted in UTC. Adding an explicit DTSTART;TZID=Europe/Paris did not appear to affect the computed next execution.
Environment
- Product: Codex Desktop app
- Platform: macOS arm64
- macOS: 26.5 (25F71)
- Local Codex version metadata:
latest_version: 0.136.0,last_checked_at: 2026-06-03T11:16:22.580528Z - Local timezone: Europe/Paris
- Date observed: 2026-06-05, when Europe/Paris is UTC+2
- Automation kind:
cron - Execution environment:
local
Reproduction
- Create or update a cron automation with this schedule:
``text``
FREQ=WEEKLY;BYDAY=FR;BYHOUR=16;BYMINUTE=0;BYSECOND=0
- Open the automation list/detail in Codex Desktop while local timezone is Europe/Paris.
- Observe that the list shows the schedule as Fridays at 16:00, but the detail panel computes the next execution as 18:00 local time.
- Try to make the timezone explicit:
``text``
DTSTART;TZID=Europe/Paris:20260605T160000
RRULE:FREQ=WEEKLY;BYDAY=FR;BYHOUR=16;BYMINUTE=0;BYSECOND=0
- The update is accepted, but the next execution still appears to be computed as 18:00 local time.
Expected behavior
A weekly automation configured for BYHOUR=16 in the user's local timezone, or with explicit DTSTART;TZID=Europe/Paris, should run at 16:00 Europe/Paris wall time. It should continue to stay at 16:00 across DST changes.
Actual behavior
The UI text says the automation is scheduled for Fridays at 16:00, but the computed next execution is 18:00 Europe/Paris on 2026-06-05. Since Paris was UTC+2 that day, this looks like BYHOUR=16 is being interpreted as 16:00 UTC.
The only workaround found was to change the stored RRULE to:
FREQ=WEEKLY;BYDAY=FR;BYHOUR=14;BYMINUTE=0;BYSECOND=0
That makes the next execution align with 16:00 Europe/Paris during DST, but it is brittle and will need to be changed to 15:00 UTC after the winter-time transition.
Impact
Scheduled automations that users configure for local wall-clock times can run one or two hours late/early depending on timezone and DST, while the UI still suggests the intended local time. This is especially confusing for automations that post messages or perform time-sensitive work.
15 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
I can reproduce the same timezone issue on Codex Desktop in Asia/Shanghai (CST, UTC+08:00), using a heartbeat automation.
Environment
/Applications/Codex.appdate '+%Y-%m-%d %H:%M:%S %Z %z'heartbeatReproduction
Update a heartbeat automation with this RRULE, intending Monday 08:15 local time:
The intended target was:
The correct Unix milliseconds for that local time are:
However, Codex computed the next run as:
That is equivalent to interpreting
BYHOUR=8as08:15 UTC, not08:15in the system local timezone. The incorrect Unix milliseconds observed were:Impact
This shifts fixed wall-clock automations by +8 hours on Asia/Shanghai systems. For time-sensitive staged workflows, the automation wakes at the wrong phase of the day even though the RRULE intent is a local wall-clock time.
Workaround
I had to manually verify and correct SQLite
automations.next_run_atto the intended local-time Unix milliseconds, then verify by convertingnext_run_at / 1000back to system local time.Expected behavior: RRULE fixed-time schedules should respect the user's system local timezone, or Codex should expose an explicit timezone/TZID setting and make the UI and underlying
next_run_atconsistent.Source code analysis confirms the dual-path timezone issue
I did a deep dive into the bundled Electron source (
src-VjjkG3q_.jsin Codex Desktop) and found the root cause. The scheduler has two code paths for computing the next run time:Path 1: Optimized LOCAL path (for WEEKLY/DAILY)
The
rc()function checks if a rule qualifies for an optimized path (noDTSTART, single clean rrule,interval===1,origOptionssubset of{freq, interval, dtstart, tzid, byweekday, byminute, byhour}). ForWEEKLY/DAILY, it uses JavaScriptnew Date(year, month, date, hour, minute)— which is LOCAL timezone.Path 2: Generic UTC path (for HOURLY and others)
Everything else falls through to
rrule.js's.after()method, which operates in UTC. TheBYHOURvalues are then interpreted as UTC hours.Why this matters
FREQ=WEEKLY;BYDAY=MO-FR;BYHOUR=0;BYMINUTE=0-> Path 1 -> fires at local midnight (00:00 CST for me in UTC+8), not 00:00 UTCFREQ=HOURLY;INTERVAL=1;BYDAY=MO-FR-> Path 2 -> fires in UTC hoursDTSTART;TZID=...is ignored because theTc()check (origOptions.dtstart == null) routes rules with DTSTART to the generic rrule.js path, but even there TZID appears to be disregardedWorkaround
I adjusted all my
BYHOURvalues to be local time for WEEKLY rules (since they take Path 1). For hourly rules, I removedBYDAYentirely so the rule takes the interval path (ac()returns3600000ms) and added a time guard in the prompt.Environment
I have another reproduction from Codex Desktop on macOS in
Asia/Shanghai(UTC+08:00), specifically with a daily heartbeat automation created from a natural-language local-time request.Environment
26.602.710360.137.0-alpha.4Asia/ShanghaiCST +0800heartbeatReproduction / observed config
The user asked, in Chinese, for a message every day at 05:30 in the morning:
The saved heartbeat automation was:
It was created at:
Expected behavior
Because the request and thread timezone are
Asia/Shanghai,BYHOUR=5;BYMINUTE=30should mean 05:30 Asia/Shanghai. Since the automation was created after 05:30 local time, the next run should have been:Actual behavior
The heartbeat fired around:
That is consistent with interpreting
BYHOUR=5as 05:30 UTC, not 05:30 in the thread/system timezone.A follow-up check around the expected local-time run window (
2026-06-09T21:30Z, i.e.2026-06-10 05:30 CST) found noautomation-3heartbeat/message records.Additional failure in the same run
The run also failed to produce a user-visible heartbeat message. The app log contains:
The corresponding session record has
last_agent_message=null, so the run completed without a final heartbeat notification. The turn context for that scheduled run still showedcollaboration_mode_kind="plan", which may be related to the heartbeat output not being emitted.Current workaround
To make the automation fire at Beijing 05:30, I had to change the RRULE to UTC time manually:
This should result in
21:30 UTC == 05:30 Asia/Shanghaithe next day, but it is confusing and fragile. The UI/config no longer matches the user's actual local-time intent.Request
Please make heartbeat automations timezone-aware, or at least make timezone handling explicit in the UI/config. For natural-language scheduling requests, the expected default is the thread/system timezone (
Asia/Shanghaihere), not UTC. If heartbeat output fails (last_agent_message=null/Heartbeat automation did not complete), that failure should also be visible in the automation UI instead of silently looking like a missed reminder.I prepared a small public-repo branch related to this issue, but GitHub currently prevents me from opening a pull request because this repository appears to limit PR creation to collaborators.
Branch/commit:
Scope: this is not the full Desktop scheduler fix, because the Desktop automation scheduler implementation does not appear to be present in the public repository. The change adds test coverage that a deferred
automation_updatedynamic tool can expose atimezonefield throughtool_searchoutput and follow-up routing.Validated locally:
cargo fmt -p codex-corecargo test -p codex-core tool_search --libcargo test -p codex-core --test all tool_search_returns_deferred_dynamic_tool_and_routes_follow_up_callIf maintainers can enable external PRs or point to the public source location for the Desktop scheduler, I can turn this into a proper PR.
Adding another repro from Asia/Shanghai with the current Codex Desktop build.
Environment
26.609.3074124G90), arm64UTC+08:00)cronlocalWhat I observed
I have two active weekly cron automations. Their persisted
automation.tomlfiles contain the intended local wall-clock schedules:However, the scheduler state in
$HOME/.codex/sqlite/codex-dev.dbcomputed the next/last run timestamps as ifBYHOURwere UTC:So:
BYHOUR=12is scheduled at about 20:00 local time in Asia/Shanghai.BYHOUR=18;BYMINUTE=30is scheduled at about 02:30 local time the next day.The automation memory/run records also show runs at those shifted local times, including a team automation run around
2026-06-05 20:07 CSTand a personal automation run around2026-06-06 02:39 CST.Additional note
In this local repro, there is also evidence of the same weekly automation running once near the intended local time and once near the UTC-interpreted local time. That looks similar to #26588, while the persisted
next_run_at/last_run_atstate matches this issue's UTC interpretation problem.Expected behavior
A weekly cron automation configured through Codex Desktop for Friday 12:00 or Friday 18:30 should run at the user's local Asia/Shanghai wall-clock time, unless the UI/API clearly documents that
BYHOURis UTC.The UI,
automation.toml, and SQLite scheduler state should agree about whether RRULE wall-clock fields are local time or UTC.Impact
For UTC+8 users, weekly automations can run eight hours late, including crossing into the next calendar day for evening schedules. This is disruptive for weekly reports, meeting-prep automations, reminders, and any time-sensitive automation.
This issue happened to me as well. I'd love to fix it if codex team allows me to (i.e. PRs are invite-only).
I have the same problem as well, even I have asked the system to set up the timezone to be Asia/Shanghai time, it still add 8 hours automatically. Don't know how to fix this problem.
<img width="1128" height="776" alt="Image" src="https://github.com/user-attachments/assets/248efcc4-661d-489e-95ce-da50c36d3704" />
Adding another reproduction from Europe/Bratislava (CEST, UTC+02:00), observed today.
Environment
+0200)codexon PATH:codex-cli 0.130.0codex-cli 0.142.4cronandheartbeatlocalLocal time verification
Shell time on the same host showed the expected local and UTC times:
Reproduction
Created a one-shot cron automation intended to run at 20:04 Bratislava time:
The saved automation was active, but no visible run/output appeared during active observation after 20:04 CEST. This looked consistent with a two-hour offset, as if the local wall-clock time was being treated as UTC.
Also tested a heartbeat automation in the same thread with a one-shot schedule. A schedule using
DTSTART;TZID=Europe/Bratislavalikewise did not wake at the expected local time. I then tried an explicit UTCDTSTART:20260630T181400Zfor the equivalent local window; that also did not produce a visible heartbeat/run during the short observation window, so the most reliable signal here is the original timezone-aware schedule missing its expected CEST window.Expected behavior
DTSTART;TZID=Europe/Bratislava:20260630T200400should run at 20:04 Europe/Bratislava local wall-clock time and remain stable across DST changes.Actual behavior
The automation did not trigger at the expected local-time window. The observed behavior matches the class of bugs described here where local timezone-aware automation schedules are effectively shifted by the UTC offset.
Impact
This affects mobile notification/reporting workflows. In my case I was setting up Codex automations to send HEY email notifications for reportable automation results. The email channel itself was verified independently via
hey compose, but the scheduled Codex automation did not fire at the expected local time.Contact
If maintainers need additional details from this repro, you can contact me at juraj@matase.sk.
I’m seeing the same issue in America/Denver.
Environment:
Schedule:
DTSTART;TZID=America/Denver:20260701T090500
RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYHOUR=9,11,13,15;BYMINUTE=5;BYSECOND=0
Expected:
Runs at 9:05, 11:05, 13:05, 15:05 America/Denver.
Observed:
Runs appeared overnight/early morning, matching BYHOUR interpreted as UTC:
9,11,13,15 UTC => 3:05,5:05,7:05,9:05 MDT.
Workaround:
DTSTART:20260701T150500Z
RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYHOUR=15,17,19,21;BYMINUTE=5;BYSECOND=0
That fixes the displayed next run during MDT, but will break at DST transition.
I have a deterministic Windows / Asia-Tokyo reproduction that extends this bug to the
YEARLYpath and a calendar-year boundary.Environment
26.707.3748.00.144.0-alpha.410.0.26200 x64Asia/Tokyo/Tokyo Standard TimeReproduction
The automation was created at
2026-07-13 00:37:02 JSTwith:The UI showed an annual schedule for July 12 at 17:15, but the card said
Next run in 2 hoursat approximately2026-07-13 00:38 JST.The stored value was:
That exactly explains the countdown: 00:38 JST to 02:15 JST is about 1 hour 37 minutes.
However, July 12 at 17:15 JST had already passed when the automation was created. The correct next occurrence is:
So the
YEARLYpath both interpretsBYHOUR=17as UTC and selects the current-year UTC occurrence instead of advancing the already-expired local occurrence to 2027.This matches the dual local/generic paths already analyzed in this thread and the +9-hour Asia/Tokyo behavior in #32304. It also reproduces on the exact Windows app build reported in #32236.
Suggested regression case:
The UI summary, stored
next_run_at, and runner should all use the same normalized schedule and IANA timezone. A fixed UTC offset would not be sufficient for DST-observing zones.Confirming this still affects actual cron execution, not only the displayed next-run time, on a current Codex Desktop build.
Environment
26.707.72221(build5307)26.5.2(25F84), arm64America/Los_Angeles/ PDT (UTC-07:00)cronlocalReproduction
I had a weekly automation intended to run Wednesday at 15:00 Los Angeles time. Its persisted recurrence included an explicit timezone:
Expected first run:
Actual task-thread creation:
The seven-hour difference exactly matches treating the persisted
15:00wall-clock value as UTC. The automation's task appeared in the local thread database at 08:01 PDT and began running then.I separately verified that the host timezone was PDT (
-0700). The automation file had not changed since July 9, so this was not caused by a schedule edit around execution time.Impact
This caused a substantial weekly workflow to start seven hours early. Because the saved schedule correctly names
America/Los_Angeles, the configuration and UI give no indication that actual execution will use UTC instead.This reproduction confirms the bug persists in build
26.707.72221and affects the runner itself.I reproduced a closely related one-time heartbeat variant on macOS 26.5.2 in
America/New_Yorkon 2026-07-17.At approximately 21:17 EDT, I created an ACTIVE heartbeat intended to run once at 23:42 EDT using:
The Automations list displayed “Daily at 11:42 PM” but reported “Next run in 23 hours,” rather than roughly 2 hours 25 minutes. The scheduler had effectively selected tomorrow despite displaying the intended local wall-clock time.
I then attempted to anchor the same future occurrence explicitly:
The automation update API rejected it with:
The machine's verified local time was 2026-07-17 21:19 EDT, so that DTSTART was still more than two hours in the future.
The equivalent UTC instant was accepted:
This appears to affect both next-run computation for a floating/local RRULE and validation of an explicit TZID DTSTART. Existing recurring automations on the same machine use local
BYHOURrules normally, so the UTC form was only a workaround for this one-time run.Adding a current Asia/Hong_Kong reproduction that narrows this to a dual-path inconsistency between
automation_updatefirst-run seeding and later RRULE evaluation.Environment:
26.715.31925, build555126.5.2build25F84, arm64Asia/Hong_Kong, UTC+08:00cron, execution environmentlocalI used the app's
automation_updatehandler to create a weekly automation with this UTC-coded workaround, intending Monday 09:30 HKT:The handler persisted the RRULE unchanged, but initialized
next_run_atas approximately2026-07-20 01:31 HKT, rather than2026-07-20 09:30 HKT. In other words, the create or update path treatedBYHOUR=01as local time, while the runner behavior documented in this issue treats it as UTC.This repeated across three independent updates:
I corrected only
automations.next_run_atto the intended HKT instants while leaving the UTC-coded RRULEs intact. The resulting pairs are now internally consistent, for exampleBYHOUR=01;BYMINUTE=30withnext_run_at=2026-07-20T01:30:00Z, displayed locally as2026-07-20 09:30 HKT.This appears to corroborate the dual-path source analysis already posted above: the mutation handler seeds the first occurrence using local wall time, while the scheduler later evaluates the same stored RRULE as UTC. That split also explains the reported risk of one run near the locally seeded time and another after UTC-based recomputation. A regression test should assert that create or update, displayed next run, persisted
next_run_at, and actual execution all use the same timezone interpretation.Adding a deterministic
Asia/Seoulreproduction that narrows the dual-path timezone issue specifically to the presence ofBYSETPOS.Environment
26.715.52143(build5591)27.0, arm64Asia/Seoul/ KST (UTC+09:00)cronControlled reproduction
I created three active cron automations seconds apart in the same local project, with the same prompt, model, and execution settings. The candidate occurrence set was intentionally kept identical.
A — baseline
UI result:
This is consistent with the next occurrence being 09:20 KST.
B — baseline plus
BYSECOND=0UI result:
This also stays on the local-time path, so
BYSECONDis not the trigger in this reproduction.C — baseline plus a no-op
BYSETPOSUI result:
The eight candidate times are 09:20, 09:30, 10:20, 10:30, 13:20, 13:30, 14:20, and 14:30. Because
BYSETPOSselects all eight positions, it is semantically a no-op and should not change the recurrence set.However, the 11-hour countdown is consistent with 09:20 being interpreted as UTC and therefore becoming 18:20 KST. The exact nine-hour shift matches the local UTC offset.
Expected behavior
All three rules should calculate the same next occurrence, 09:20 KST. Adding a no-op
BYSETPOSmust not change the timezone used to evaluateBYHOUR.Actual behavior
Adding
BYSETPOSalone changes the next-run calculation from local time to UTC, even though it does not remove any occurrence. This appears to provide a minimal regression case for the optimized-local versus generic-UTC path described in the earlier source analysis.This reproduction verifies the displayed next-run calculation. I did not wait for the automations to execute.