Skip to content

fix(usage): reject invalid explicit dates in usage RPC date parsing#93745

Merged
vincentkoc merged 2 commits into
openclaw:mainfrom
harjothkhara:fix/usage-reject-invalid-calendar-dates
Jun 16, 2026
Merged

fix(usage): reject invalid explicit dates in usage RPC date parsing#93745
vincentkoc merged 2 commits into
openclaw:mainfrom
harjothkhara:fix/usage-reject-invalid-calendar-dates

Conversation

@harjothkhara

@harjothkhara harjothkhara commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

usage.cost and sessions.usage accepted shape-valid but impossible dates
(e.g. 2026-02-30). parseDateParts validated only the YYYY-MM-DD regex, so
Date.* silently rolled them over (2026-02-302026-03-02) and the RPC
returned cost/usage for the wrong day with success: true.

This PR:

  • Rejects out-of-range calendar dates in parseDateParts via a UTC round-trip probe (kills the silent rollover; also catches the JS 2-digit-year remap).
  • Returns INVALID_REQUEST from usage.cost / sessions.usage when an explicitly provided startDate/endDate is unparseable (bad format or impossible calendar date), instead of silently falling back to the default 30-day range. This completes the fail-closed contract sessions.usage already had for malformed-format dates (its schema rejects format but not out-of-range), and adds it to usage.cost, which had no date validation.
  • Leaves absent/valid dates unchanged.

Out of scope: the gateway-protocol schema (pattern-only) is unchanged — validation lives at the single choke point both RPCs share. Two incidental oxfmt-0.52 line-wraps on pre-existing >100-char lines in the touched files.

Linked context

No linked issue — self-found bug. Closes: none.

Real behavior proof

  • Behavior or issue addressed: usage.cost / sessions.usage silently returned cost/usage for the wrong day when given an impossible-but-shape-valid date (e.g. 2026-02-302026-03-02); now rejected with INVALID_REQUEST.
  • Real environment tested: local checkout at upstream/main 3630ce6cbb, Node 22.22.3, isolated git worktree; drove the real usage.cost RPC handler and the exported date helpers via tsx (no mocks of the parse/handler logic), importing the actual src/gateway/server-methods/usage.ts from current main (before) and this branch (after).
  • Exact steps or command run after this patch: ran a tsx script invoking usageHandlers["usage.cost"]({ params: { startDate: "2026-02-30" }, context: { getRuntimeConfig: () => ({}) } }) plus the findInvalidExplicitDate / parseDateToMs helpers, against both the unfixed main copy and this branch.
  • Evidence after fix (copied live output from the real handler):
== BEFORE (current main, unfixed) ==
  usage.cost would query startDate "2026-02-30" as day = 2026-03-02   <-- WRONG day, silent, success

== AFTER (this branch) ==
  findInvalidExplicitDate({ startDate: "2026-02-30" })                         -> "startDate"
  findInvalidExplicitDate({ startDate: "2026-02-01", endDate: "2026-02-02" })  -> undefined   (valid dates untouched)
  usage.cost RPC for startDate "2026-02-30"  ->  ok=false,
      error = {"code":"INVALID_REQUEST","message":"invalid startDate: expected a valid YYYY-MM-DD calendar date"}
  • Observed result after fix: the impossible date is rejected at the RPC boundary with INVALID_REQUEST instead of returning a successful response for the wrong/default range; valid dates and the real leap day 2024-02-29 are unaffected.
  • What was not tested: a full live gateway server with an over-the-wire client roundtrip (drove the real handler function directly instead).
  • Proof limitations or environment constraints: this is a gateway RPC with no UI surface, so the evidence is copied live terminal output from the real handler rather than a screenshot; an end-to-end gateway + client deployment was not stood up locally.

Tests and validation

  • node scripts/run-vitest.mjs src/gateway/server-methods/usage.test.ts — 38/38 pass (added: out-of-range rejection + leap-day guard, findInvalidExplicitDate unit test, handler-level INVALID_REQUEST test).
  • tsgo:core + tsgo:core:test, oxlint, oxfmt --check — clean.
  • Cross-model review: codex review — clean, 0 findings (Claude implemented → Codex reviewed).

Risk checklist

  • User-visible behavior change? Yes — invalid explicit dates now return INVALID_REQUEST instead of a silent default-range success.
  • Config, environment, or migration change? No.
  • Security, auth, secrets, network, or tool-execution change? No.
  • Highest-risk area / mitigation: usage.cost previously had no date validation, so this adds a fail-closed path. Mitigated by rejecting only explicitly provided unparseable dates — absent/valid dates behave exactly as before — and covered by the handler-level test.

AI-assisted: planned and implemented by Claude (Opus 4.8), reviewed by Codex (gpt-5.5).

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: S triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels Jun 16, 2026
@harjothkhara harjothkhara changed the title fix(usage): reject invalid explicit dates in usage RPC date parsing [AI-assisted] fix(usage): reject invalid explicit dates in usage RPC date parsing Jun 16, 2026
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels Jun 16, 2026
@clawsweeper

clawsweeper Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 16, 2026, 3:14 PM ET / 19:14 UTC.

Summary
The PR adds calendar-date round-trip validation to the gateway usage date parser, rejects explicit invalid startDate/endDate values in usage.cost and sessions.usage, and adds regression tests.

PR surface: Source +58, Tests +44. Total +102 across 2 files.

Reproducibility: yes. Source inspection plus a Node Date.UTC probe shows current main and v2026.6.8 can roll 2026-02-30 to 2026-03-02; the PR body also supplies copied live output from the real handler path.

Review metrics: 1 noteworthy metric.

  • RPC date validation behavior: 2 methods changed to reject explicit invalid dates. Both usage.cost and sessions.usage are gateway API surfaces, so the stricter failure mode matters before merge.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P2] Maintainer should explicitly accept the INVALID_REQUEST behavior change for impossible explicit usage dates before merge.

Risk before merge

  • [P1] Compatibility: gateway API clients that currently send impossible explicit date strings and receive a successful default or rolled range will now receive INVALID_REQUEST; this is likely the intended bug fix, but it is still a stricter fail-closed behavior maintainers should accept before merge.

Maintainer options:

  1. Accept Narrow Fail-Closed Behavior (recommended)
    Merge after maintainer acceptance that impossible explicit usage dates now error instead of returning a default or rolled range.
  2. Ask For Wire-Level Smoke
    If maintainers want extra API assurance, request a gateway client roundtrip for usage.cost and sessions.usage showing INVALID_REQUEST for an impossible date.

Next step before merge

  • No automated repair is needed; the remaining action is maintainer acceptance of the narrow fail-closed RPC compatibility change and normal merge validation.

Security
Cleared: The diff only changes gateway usage date parsing/handler logic and focused tests; it does not touch secrets, dependencies, CI, packaging, or code-execution surfaces.

Review details

Best possible solution:

Merge the shared parser and handler-boundary rejection once maintainers accept the narrow INVALID_REQUEST compatibility change for impossible explicit usage dates.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection plus a Node Date.UTC probe shows current main and v2026.6.8 can roll 2026-02-30 to 2026-03-02; the PR body also supplies copied live output from the real handler path.

Is this the best way to solve the issue?

Yes. The shared parser plus RPC-boundary rejection is the narrow maintainable fix because both affected handlers already share the date parser/range path, and leaving the protocol schema pattern-only avoids unnecessary protocol churn.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against c6b1921a91bf.

Label changes

Label changes:

  • add P2: This fixes a normal-priority gateway usage correctness bug with limited blast radius and focused tests.
  • add merge-risk: 🚨 compatibility: The PR deliberately changes invalid explicit usage date strings from successful fallback/rollover behavior to INVALID_REQUEST.
  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes copied live terminal output from the real usage.cost handler and exported date helpers before and after the patch, which is adequate proof for this non-visual gateway RPC behavior.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes copied live terminal output from the real usage.cost handler and exported date helpers before and after the patch, which is adequate proof for this non-visual gateway RPC behavior.

Label justifications:

  • P2: This fixes a normal-priority gateway usage correctness bug with limited blast radius and focused tests.
  • merge-risk: 🚨 compatibility: The PR deliberately changes invalid explicit usage date strings from successful fallback/rollover behavior to INVALID_REQUEST.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes copied live terminal output from the real usage.cost handler and exported date helpers before and after the patch, which is adequate proof for this non-visual gateway RPC behavior.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes copied live terminal output from the real usage.cost handler and exported date helpers before and after the patch, which is adequate proof for this non-visual gateway RPC behavior.
Evidence reviewed

PR surface:

Source +58, Tests +44. Total +102 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 59 1 +58
Tests 1 45 1 +44
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 104 2 +102

What I checked:

  • Current main accepts shape-valid impossible dates: On current main, parseDateParts only matches YYYY-MM-DD, converts year/month/day to numbers, and returns them without a calendar round-trip check. (src/gateway/server-methods/usage.ts:166, c6b1921a91bf)
  • Current handler path has no explicit invalid-date rejection: Current usage.cost and sessions.usage pass start/end values into parseDateRange; usage.cost has no schema validation, and sessions.usage only rejects schema-invalid parameters before range parsing. (src/gateway/server-methods/usage.ts:924, c6b1921a91bf)
  • Latest release has the same unfixed behavior: Tag v2026.6.8 points at 844f405, and its usage handler still lacks the PR's round-trip date validation and explicit invalid-date rejection. (src/gateway/server-methods/usage.ts:166, 844f405ac1be)
  • Runtime contract proof for rollover: Node's Date.UTC(2026, 1, 30) produces 2026-03-02T00:00:00.000Z, confirming the silent rollover behavior the PR guards against.
  • PR implementation rejects invalid explicit dates at the shared boundary: The PR adds a UTC round-trip probe to parseDateParts, introduces findInvalidExplicitDate, and calls it before both usage.cost and sessions.usage query usage ranges. (src/gateway/server-methods/usage.ts:183, 803649738783)
  • PR test coverage exercises parser and handler behavior: The branch adds invalid calendar date, leap-day, helper, and usage.cost handler rejection coverage, including an assertion that rejected requests do not call the cost loader. (src/gateway/server-methods/usage.test.ts:97, 803649738783)

Likely related people:

  • steipete: Peter Steinberger is the heaviest recent contributor across the usage gateway, protocol, and UI surfaces sampled, and authored earlier provider usage/cost summary work that this parser supports. (role: feature-history owner; confidence: high; commits: 3ce1ee84ace4, 0e17e55be902, d358b3ac88b2; files: src/gateway/server-methods/usage.ts, ui/src/ui/controllers/usage.ts, packages/gateway-protocol/src/schema/sessions.ts)
  • Tak Hoffman: Tak Hoffman authored the original Web UI token usage dashboard and later multi-agent sessions.usage discovery work, making them relevant to usage RPC behavior and caller expectations. (role: introduced adjacent usage surfaces; confidence: high; commits: 8a352c8f9dfd, 9271fcb3d49f, 7a7e4cd4c481; files: src/gateway/server-methods/usage.ts, src/gateway/server-methods/usage.sessions-usage.test.ts, ui/src/ui/views/usage.ts)
  • huntharo: Harold Hunt authored the selected-date timezone behavior work that made explicit usage date interpretation a central user-facing path. (role: explicit-date behavior contributor; confidence: medium; commits: 844d84a7f53b; files: src/gateway/server-methods/usage.ts, src/gateway/server-methods/usage.test.ts, ui/src/ui/controllers/usage.ts)
  • Mason Huang: The current parser and handler lines blame to Mason Huang's recent gateway usage file addition/refactor on current main, so they are a useful recent routing signal for this exact code location. (role: recent area contributor; confidence: medium; commits: f046d7aa23df; files: src/gateway/server-methods/usage.ts, src/gateway/server-methods/usage.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jun 16, 2026
@vincentkoc vincentkoc self-assigned this Jun 16, 2026
harjothkhara and others added 2 commits June 17, 2026 03:50
usage.cost and sessions.usage accepted shape-valid but impossible dates such as 2026-02-30: parseDateParts validated only the YYYY-MM-DD regex, so Date.* silently rolled them over (2026-02-30 -> 2026-03-02) and the RPC returned cost/usage for the wrong day. Out-of-range parts now fail a UTC round-trip check, and an explicitly provided unparseable date (bad format or impossible calendar date) returns INVALID_REQUEST instead of silently falling back to the default range. Absent/valid dates are unchanged.

[AI-assisted]
@vincentkoc
vincentkoc force-pushed the fix/usage-reject-invalid-calendar-dates branch from 8036497 to ead2ae8 Compare June 16, 2026 19:50
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 16, 2026
@vincentkoc

Copy link
Copy Markdown
Member

Maintainer verification complete. Fresh structured review caught one real edge in the contributor patch: explicit non-string date values could still silently fall through to the default usage range. I repaired that on the contributor branch, rebased it onto current main, and reran proof.

Verified:

  • node scripts/run-vitest.mjs src/gateway/server-methods/usage.test.ts — 38 passed
  • git diff --check origin/main...HEAD — clean
  • fresh structured autoreview after the repair — clean
  • PR CI — 136 passed, 0 pending, 0 failed

The handlers now reject impossible calendar dates and any explicitly supplied unparseable date value before querying usage; blank/null dates remain absent. Ready to squash merge.

@vincentkoc
vincentkoc merged commit a3f3b04 into openclaw:main Jun 16, 2026
167 checks passed
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 17, 2026
…penclaw#93745)

* fix(usage): reject invalid explicit dates in usage RPC date parsing

usage.cost and sessions.usage accepted shape-valid but impossible dates such as 2026-02-30: parseDateParts validated only the YYYY-MM-DD regex, so Date.* silently rolled them over (2026-02-30 -> 2026-03-02) and the RPC returned cost/usage for the wrong day. Out-of-range parts now fail a UTC round-trip check, and an explicitly provided unparseable date (bad format or impossible calendar date) returns INVALID_REQUEST instead of silently falling back to the default range. Absent/valid dates are unchanged.

[AI-assisted]

* fix(usage): reject non-string explicit dates

---------

Co-authored-by: Vincent Koc <[email protected]>
crh-code pushed a commit to crh-code/openclaw that referenced this pull request Jun 18, 2026
…penclaw#93745)

* fix(usage): reject invalid explicit dates in usage RPC date parsing

usage.cost and sessions.usage accepted shape-valid but impossible dates such as 2026-02-30: parseDateParts validated only the YYYY-MM-DD regex, so Date.* silently rolled them over (2026-02-30 -> 2026-03-02) and the RPC returned cost/usage for the wrong day. Out-of-range parts now fail a UTC round-trip check, and an explicitly provided unparseable date (bad format or impossible calendar date) returns INVALID_REQUEST instead of silently falling back to the default range. Absent/valid dates are unchanged.

[AI-assisted]

* fix(usage): reject non-string explicit dates

---------

Co-authored-by: Vincent Koc <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants