Repository navigation
Description
Problem
Form string fields with format: "date-time" accept calendar-invalid dates. In packages/core/src/form.ts:
function isDate(value: string) {
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false
const date = new Date(`${value}T00:00:00.000Z`)
return !Number.isNaN(date.getTime()) && date.toISOString().slice(0, 10) === value
}
function isDateTime(value: string) {
return !Number.isNaN(new Date(value).getTime())
}The sibling date format (form.ts:368-372) round-trips the components, so 2026-02-30 is correctly rejected there. isDateTime (form.ts:374-376) only checks that new Date(value) is not NaN — and Date parsing silently rolls day overflow into the next month, so a date-time answer of 2026-02-30T10:00:00Z passes validation (validateField, form.ts:334) even though that instant does not exist. Whatever consumes the answer then parses a different day than the one that was entered.
Evidence (verified on main 4a50304, 2026-10-28)
Bun probe on the laptop running the real validateAnswer from @opencode/core/form via a temporary bun test in packages/core (removed after; probe kept at laptop ~/git/fleet/probe-2026-10-27-resetdt/probe.ts; form.ts md5 6cf0564198d0426874cb5daeb8505dfc, byte-identical on both checkouts). For a required string field with format: "date-time", validateAnswer returned undefined (accepted) for:
2026-02-30T10:00:00Z— Feb 30 does not exist (rolls to Mar 2)2026-02-29T10:00:00Z— 2026 is not a leap year (rolls to Mar 1)2026-04-31T10:00:00Z— April has 30 days (rolls to May 1)
Controls behaved correctly:
2026-01-01T25:00:00Zand2026-01-01T10:60:00Z— rejected (time overflow isNaN, unlike day overflow: the asymmetry is exactly the calendar-day component)2024-02-29T10:00:00Z(leap year) and2026-01-15T10:30:00Z— acceptednot-a-date— rejected- The same values under
format: "date":2026-02-30rejected,2024-02-29accepted
Also observed, left for triage rather than asserted as part of this bug: isDateTime accepts non-ISO forms such as Jan 5 2026 and 2026/01/15 10:30, because it imposes no shape at all. Whether date-time should require ISO 8601 shape is a separate contract question; this issue is only about calendar-invalid ISO dates being accepted.
Existing tests (packages/core/test/form.test.ts) cover URI format validation but have no date / date-time format cases at all.
Why it matters
Form answers are user/model-supplied structured data that downstream code trusts as validated: a field declared date-time is the system's promise that the value is a real instant. Accepting Feb 30 silently shifts the instant by days when anything re-parses it — the same rollover class fixed for the quota-reset parsers in #609 and for MessagingTemplate.parseTime in #647, and already guarded for this file's own date format.
Acceptance criteria
validateAnswerrejects calendar-invaliddate-timevalues — at least2026-02-30T…,2026-02-29T…in a non-leap year, and2026-04-31T…— withExpected date-time for form field: <key>.- Valid values keep passing, including leap-day
2024-02-29T…, ordinary ISO date-times with offsets, and the currently accepted time-overflow rejections stay rejected. - The check round-trips the date components (the technique
isDatein the same file andModelRouteLimits.parseAbsoluteTimealready use), applied only when the value carries an ISOYYYY-MM-DDdate part so non-ISO leniency is not silently changed by this fix. - New tests in packages/core/test/form.test.ts cover
date-timecalendar-invalid and leap-valid cases, and pin the existingdateformat behaviour alongside them.
Activity
developing-today commentedon Oct 28, 2026
OPENCODE-119
dezren39 commentedon Oct 28, 2026
🤖 Agent note · filed
dezren39 commentedon Oct 28, 2026
agent-note:v1 — PARKED by repo-queue-worker-shift-opencode-b (Lane B, 2026-10-27 CDT)
queue/1591-656-form-datetime, commit 8f12622, laptop worktree~/git/fleet/1591-656-form-datetime):isDateTimeround-trips the ISO date part so Feb 30 / non-leap Feb 29 / Apr 31 are rejected; non-ISO leniency, valid ISO, offsets, and leap day unchanged; new date/date-time tests inform.test.ts.bun test test/form.test.ts— 15 pass.dezren39 commentedon Oct 28, 2026
Merged via PR #658 (squash, merge commit
205db4ec). CI verified this shift: typecheck, build (linux+windows), check-standards, check-compliance, nix-eval all pass; unit (linux+windows) fail only on the two pre-existing TUI permission tests (permission layers leave the focused peer's Enter…,permission rejection text keeps its draft…) — no form test failures. Task 1591 completed.agent-note:v1 — lane repo-queue-worker-shift-opencode-b, task 1591, state: complete