Skip to content

fix(core): Form date-time format accepts calendar-invalid dates — '2026-02-30T10:00:00Z' passes validation #656

Closed

Description

@dezren39

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:00Z and 2026-01-01T10:60:00Z — rejected (time overflow is NaN, unlike day overflow: the asymmetry is exactly the calendar-day component)
  • 2024-02-29T10:00:00Z (leap year) and 2026-01-15T10:30:00Z — accepted
  • not-a-date — rejected
  • The same values under format: "date": 2026-02-30 rejected, 2024-02-29 accepted

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

  • validateAnswer rejects calendar-invalid date-time values — at least 2026-02-30T…, 2026-02-29T… in a non-leap year, and 2026-04-31T… — with Expected 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 isDate in the same file and ModelRouteLimits.parseAbsoluteTime already use), applied only when the value carries an ISO YYYY-MM-DD date part so non-ISO leniency is not silently changed by this fix.
  • New tests in packages/core/test/form.test.ts cover date-time calendar-invalid and leap-valid cases, and pin the existing date format behaviour alongside them.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/small-fixQueue packet kind: bounded fix with reproduction and targeted tests
    on Oct 28, 2026
  2. developing-today commented on Oct 28, 2026

    @developing-today
  3. added
    source/creatorFiled by the new-issue-creator job
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    app/corePackage: core (pkgs/ or apps/ in this repo)
    effort/sSmall: expected under 2 hours of work
    on Oct 28, 2026
  4. dezren39 commented on Oct 28, 2026

    @dezren39
    Author

    🤖 Agent note · filed

    • Agent: new-issue-creator-opencode
    • Session / ID: cron job new-issue-creator-opencode, run 2026-10-27 19:42 CDT
    • Found via: filed this run
    • Task: Filed this issue after verifying the bug on current main with a laptop bun probe running the real Form validateAnswer.
    • Expectation: effort/s — give isDateTime the same calendar round-trip the sibling isDate already has (ISO date part only), plus date/date-time format tests; done when the acceptance criteria in the issue body pass.
    • Links: queue packet packet-656.json (repo-health-opencode-pilot)
  5. added
    queue/claimedA queue worker has claimed the task for this item
    work/assignedAn agent holds this item via the queue (claimed task)
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 28, 2026
  6. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    and removed
    queue/claimedA queue worker has claimed the task for this item
    on Oct 28, 2026
  7. dezren39 commented on Oct 28, 2026

    @dezren39
    Author

    agent-note:v1 — PARKED by repo-queue-worker-shift-opencode-b (Lane B, 2026-10-27 CDT)

  8. added
    queue/claimedA queue worker has claimed the task for this item
    and removed
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Oct 28, 2026
  9. removed
    queue/claimedA queue worker has claimed the task for this item
    work/assignedAn agent holds this item via the queue (claimed task)
    on Oct 28, 2026
  10. dezren39 commented on Oct 28, 2026

    @dezren39
    Author

    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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    app/corePackage: core (pkgs/ or apps/ in this repo)effort/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobstatus/in-progressActively being workedstatus/needs-triageNot yet triaged; needs a first lookstatus:donetask/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions