diff --git a/README.md b/README.md
index f4691424e..f336b3f36 100644
--- a/README.md
+++ b/README.md
@@ -1782,6 +1782,7 @@ Editing a one-time task's title or prompt preserves its original execution time,
Current MVP capabilities:
- Manage tasks at `/workspace/scheduled-tasks`
+- One-time task forms reject local times skipped by daylight-saving transitions; select another time before creating or saving the task.
- Choose whether each scheduled task reuses a thread and its conversation history or creates a fresh thread per run
- Pin each task to `lead_agent` (default) or a custom agent the owner already has; unknown names are rejected
- Duplicate an existing task into the create form as an editable draft without copying its run history
diff --git a/frontend/AGENTS.md b/frontend/AGENTS.md
index 1ee1d215f..dad982307 100644
--- a/frontend/AGENTS.md
+++ b/frontend/AGENTS.md
@@ -127,6 +127,13 @@ the standalone server from `frontend/` with `node --env-file=.env
To reach a dev server on anything other than localhost — a LAN address, or a proxied hostname — list the host in `DEER_FLOW_DEV_ALLOWED_ORIGINS` (comma-separated; a full URL is reduced to its host). It feeds Next's `allowedDevOrigins`, which gates `/_next/*`, fonts, and HMR. Without it those requests get a 403 and the page renders server-side but never hydrates, so nothing on it — including the login form — responds. Development only; production builds ignore it.
+One-time schedule input uses `validZonedLocalToUtcIso` to reject wall times that
+do not round-trip in the selected timezone. Invalid input emits an empty spec and
+localized inline feedback; both create and edit must block submission. Keep this
+UI validation separate from the API payload. Preserve the original instant when
+wall time and timezone match the mounted snapshot; validate changed inputs, and
+restore the exact original timestamp when those edits are reverted.
+
## Resources
- [LangGraph Documentation](https://langchain-ai.github.io/langgraph/)
diff --git a/frontend/src/app/workspace/scheduled-tasks/page.tsx b/frontend/src/app/workspace/scheduled-tasks/page.tsx
index 968ac79d7..7c2a53aab 100644
--- a/frontend/src/app/workspace/scheduled-tasks/page.tsx
+++ b/frontend/src/app/workspace/scheduled-tasks/page.tsx
@@ -655,6 +655,8 @@ export default function ScheduledTasksPage() {
diff --git a/frontend/src/components/workspace/scheduled-task-schedule-input.tsx b/frontend/src/components/workspace/scheduled-task-schedule-input.tsx
index 2d0c15709..865952d99 100644
--- a/frontend/src/components/workspace/scheduled-task-schedule-input.tsx
+++ b/frontend/src/components/workspace/scheduled-task-schedule-input.tsx
@@ -24,7 +24,7 @@ import {
serializeCron,
utcToZonedLocalInput,
WEEKDAYS,
- zonedLocalToUtcIso,
+ validZonedLocalToUtcIso,
type CronParts,
type CronPreset,
type IntervalUnit,
@@ -150,6 +150,11 @@ export function ScheduledTaskScheduleInput({
);
const [intervalEdited, setIntervalEdited] = useState(false);
+ const onceRunAt = runAtLocal
+ ? validZonedLocalToUtcIso(runAtLocal, timezone)
+ : null;
+ const invalidOnceTime = scheduleType === "once" && !!runAtLocal && !onceRunAt;
+
// Hold the latest onChange in a ref so the effect below does not depend on
// it. This avoids a re-render loop: if the parent passes an inline
// onChange (new reference each render), depending on it directly would
@@ -164,11 +169,7 @@ export function ScheduledTaskScheduleInput({
if (scheduleType === "once") {
const unchanged =
runAtLocal === initialOnce?.local && timezone === initialOnce.timezone;
- const runAt = unchanged
- ? initialOnce.runAt
- : runAtLocal
- ? zonedLocalToUtcIso(runAtLocal, timezone)
- : "";
+ const runAt = unchanged ? initialOnce.runAt : onceRunAt;
onChangeRef.current({
schedule_type: "once",
schedule_spec: runAt ? { run_at: runAt } : {},
@@ -203,6 +204,7 @@ export function ScheduledTaskScheduleInput({
scheduleType,
preset,
parts,
+ onceRunAt,
runAtLocal,
timezone,
initialOnce,
@@ -451,6 +453,7 @@ export function ScheduledTaskScheduleInput({
value={runAtLocal}
onChange={(e) => setRunAtLocal(e.target.value)}
aria-label={labels.fields.runAt}
+ aria-invalid={invalidOnceTime}
/>
)}
@@ -467,6 +470,11 @@ export function ScheduledTaskScheduleInput({
+ {invalidOnceTime && (
+