Skip to content

fix(sakana): parse weekly/5-hour reset time as UTC, not device-local#1827

Open
ss251 wants to merge 3 commits into
steipete:mainfrom
ss251:fix/sakana-weekly-reset-timezone
Open

fix(sakana): parse weekly/5-hour reset time as UTC, not device-local#1827
ss251 wants to merge 3 commits into
steipete:mainfrom
ss251:fix/sakana-weekly-reset-timezone

Conversation

@ss251

@ss251 ss251 commented Jul 1, 2026

Copy link
Copy Markdown

Fixes #1826.

Summary

console.sakana.ai/billing always server-renders "Resets on " in UTC — confirmed by the raw HTML's embedded "timeZone":"UTC" hydration marker and Sakana's own "Weekly usage resets every Monday at 00:00 UTC" copy elsewhere on the page. The browser only corrects that string to the viewer's local timezone client-side, after JS hydration, which SakanaUsageFetcher (an HTML-only fetcher) never runs.

parseResetDate parsed the raw string using TimeZone.current instead of UTC, so every non-UTC user got a reset countdown off by exactly their UTC offset.

  • Removes the timeZone parameter from parseBillingHTML/parseWindow/parseResetDate entirely — there's no legitimate caller-supplied value here, the source text is always UTC — and hardcodes UTC in the formatter.
  • Updates the existing fixture tests, which previously asserted the buggy local-timezone interpretation (via a shanghaiTimeZone test parameter) as if it were correct.
  • Adds a regression test pinned to a literal Unix timestamp, independent of the date-building test helper, so a future refactor of the helper itself can't silently reintroduce the bug.

Verification

I built this branch (and, for comparison, unmodified main) into the codexbar CLI and ran both against a real Sakana account (Standard plan, active weekly window) with a live cookie, comparing to the same account's billing page rendered in a real browser at the same moment:

Browser (ground truth) main (before) This branch (after)
Weekly reset July 6, 2026, 5:30 AM IST (2026-07-06T00:00:00Z) 2026-07-05T18:30:00Z (5.5h early) 2026-07-06T00:00:00Z

Plan name, 5-hour %, and weekly % were already correct in both and are untouched by this change.

Test plan

  • swift build --target CodexBarCore / swift build --target CodexBar — clean
  • ./Scripts/lint.sh format — 0 files reformatted
  • ./Scripts/lint.sh lint (swiftlint --strict) — 0 violations
  • swift test --filter SakanaUsageFetcherTests — 12/12 pass
  • Verified against a live Sakana account via the built codexbar CLI before and after the fix (table above)

console.sakana.ai/billing always server-renders "Resets on <date>" in
UTC (confirmed via the raw HTML's embedded "timeZone":"UTC" hydration
payload marker, and Sakana's own "Weekly usage resets every Monday at
00:00 UTC" copy elsewhere on the page). The browser only corrects that
text to the viewer's local timezone client-side, after JS hydration --
which this HTML-only fetcher never runs.

SakanaUsageFetcher.parseResetDate parsed the raw string using
TimeZone.current instead, so every non-UTC user got a reset time off
by exactly their UTC offset -- 5.5h early on an IST machine, verified
by building main into the codexbar CLI and running it against a real
account: the tool's own JSON output landed 5.5h before the true reset
instant shown in a browser on the same page at the same moment.

Removes the timeZone parameter entirely (it no longer has a legitimate
caller-supplied value; the source data is always UTC) and hardcodes
UTC in parseResetDate. Updates the existing fixture-based tests, which
previously asserted the buggy local-timezone interpretation as if it
were correct, and adds a regression test pinned to a literal Unix
timestamp independent of the date-building helper.

Fixes steipete#1826.
@clawsweeper

clawsweeper Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codex review: needs maintainer review before merge. Reviewed July 1, 2026, 5:16 PM ET / 21:16 UTC.

Summary
The branch removes caller-supplied timezone parsing for Sakana billing reset text, hardcodes UTC parsing, serializes and updates Sakana parser tests, and updates Sakana docs.

Reproducibility: yes. Current main passes TimeZone.current into the Sakana reset DateFormatter, and the linked issue/PR body provides raw HTML, browser, and CLI evidence that the source text is UTC.

Review metrics: 3 noteworthy metrics.

  • Touched surface: 3 files, +40/-21. The diff stays scoped to the Sakana parser, its focused tests, and Sakana docs.
  • Visible status checks: 1 successful check run. Repository-level Swift validation is not visible in GitHub’s status rollup even though the PR body reports focused validation.
  • Global timezone test: 1 NSTimeZone.default mutation added. The new regression test mutates process-global timezone state, and the suite is marked serialized to match the existing repository pattern.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #1826
Summary: This PR is the candidate fix for the linked Sakana UTC reset parsing bug.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
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] Confirm repository-level make check and make test or equivalent CI before merge if maintainers require the standard handoff checks.

Risk before merge

  • [P1] The PR body reports focused builds, lint, Sakana tests, and live validation, but GitHub’s visible status rollup only shows GitGuardian; maintainers may still want repository-level make check and make test or equivalent CI before merge.

Maintainer options:

  1. Decide the mitigation before merge
    Land the UTC parser, focused regression coverage, and docs update after normal maintainer validation so the linked Sakana bug closes through the merge.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • No automated repair remains; the next action is ordinary maintainer merge validation for the linked Sakana bug fix.

Security
Cleared: The diff is limited to parser timezone handling, focused tests, and docs; it adds no dependency, workflow, secret-handling, or supply-chain surface.

Review details

Best possible solution:

Land the UTC parser, focused regression coverage, and docs update after normal maintainer validation so the linked Sakana bug closes through the merge.

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

Yes. Current main passes TimeZone.current into the Sakana reset DateFormatter, and the linked issue/PR body provides raw HTML, browser, and CLI evidence that the source text is UTC.

Is this the best way to solve the issue?

Yes. Parsing the raw server-rendered Sakana reset text as UTC and removing the caller-supplied timezone is the narrow maintainable fix; a new setting or product choice is not needed.

AGENTS.md: found and applied where relevant.

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

Label changes

Label changes:

  • remove merge-risk: 🚨 automation: Current PR review selected no merge-risk labels.

Label justifications:

  • P2: This is a normal provider-specific bug fix with real but limited user-visible countdown impact for non-UTC Sakana users.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster 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 live before/after CLI output against a real Sakana account plus browser ground-truth comparison for the reset timestamp.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes live before/after CLI output against a real Sakana account plus browser ground-truth comparison for the reset timestamp.
Evidence reviewed

What I checked:

  • Repository policy read: AGENTS.md was read fully; its focused parser-test guidance, make check/test expectation, and warning against live provider probes without explicit request shaped this read-only review. (AGENTS.md:31, c3d33308ac06)
  • Current main still has the reported behavior: Current main defaults Sakana parseBillingHTML to TimeZone.current, passes that through parseWindow, and assigns it to the reset DateFormatter. (Sources/CodexBarCore/Providers/Sakana/SakanaUsageFetcher.swift:141, c3d33308ac06)
  • PR implements UTC reset parsing: The PR head removes the parser timezone parameter path and sets the Sakana reset formatter timezone to UTC. (Sources/CodexBarCore/Providers/Sakana/SakanaUsageFetcher.swift:227, af0951112a0a)
  • Regression coverage pins non-UTC behavior: The PR head marks SakanaUsageFetcherTests serialized and adds a regression test that forces NSTimeZone.default to UTC+14 before parsing the fixture. (Tests/CodexBarTests/SakanaUsageFetcherTests.swift:8, af0951112a0a)
  • Docs now match UTC behavior: The Sakana guide now states reset dates are parsed as UTC instead of the device-local timezone and links the linked bug report. (docs/sakana.md:39, af0951112a0a)
  • Contributor proof: The PR body reports before/after CLI output against a real Sakana account, compared to browser ground truth for the same reset instant; the linked issue includes raw HTML/browser evidence for the UTC source text. (af0951112a0a)

Likely related people:

  • steipete: Current-main blame points the Sakana parser and tests to the merged Sakana provider integration, and recent docs history includes follow-up alignment work. (role: introduced behavior and recent area contributor; confidence: high; commits: 87635bcc755b, f39879f0c4a7; files: Sources/CodexBarCore/Providers/Sakana/SakanaUsageFetcher.swift, Tests/CodexBarTests/SakanaUsageFetcherTests.swift, docs/sakana.md)
  • LeoLin990405: The merged Sakana provider PR body credits the original implementation to this contributor while preserving attribution in the integration history. (role: original Sakana implementation contributor; confidence: medium; commits: 87635bcc755b; files: Sources/CodexBarCore/Providers/Sakana/SakanaUsageFetcher.swift, Sources/CodexBarCore/Providers/Sakana/SakanaProviderDescriptor.swift, Tests/CodexBarTests/SakanaUsageFetcherTests.swift)
  • kiranmagic7: Recent Sakana docs commits added and corrected the reset-time wording that this PR updates for UTC parsing. (role: recent adjacent docs contributor; confidence: medium; commits: 5e305d3df995, 700cf1bf65ae; files: docs/sakana.md)
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 Contributor real behavior proof is sufficient. 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 priority bug or improvement with limited blast radius. labels Jul 1, 2026
- The UTC regression test didn't actually pin the fix: on a UTC CI
  runner, the pre-fix TimeZone.current code would coincidentally
  still produce the correct instant, so the test would pass either
  way. Force NSTimeZone.default to UTC+14 for the test's duration
  (same pattern as BedrockUsageStatsTests) so it only passes when the
  parser genuinely ignores the device timezone. Verified by
  temporarily reverting the fix locally and confirming the test now
  fails with the expected 5.5h-off result before restoring it.
- docs/sakana.md still said reset dates use the device's local time
  zone (TimeZone.current) -- exactly the behavior this PR removes.
  Updated to describe the actual UTC parsing and why, linking steipete#1826.
@ss251

ss251 commented Jul 1, 2026

Copy link
Copy Markdown
Author

Addressed both P3 findings from clawsweeper's review:

  • Regression test now actually pins the fix. The original test would have passed on a UTC CI runner even against the pre-fix TimeZone.current code (since TimeZone.current == UTC there). Now forces NSTimeZone.default to UTC+14 for the test's duration (same pattern as BedrockUsageStatsTests). Verified locally by temporarily reverting the fix and confirming the test fails with the expected 5.5h-off result, then restored it.
  • docs/sakana.md updated — it still said reset dates are parsed using the device's local time zone (TimeZone.current), exactly the behavior this PR removes. Now describes the actual UTC parsing and links Sakana AI: weekly/5-hour quota reset time is off by the viewer's UTC offset #1826.

12/12 Sakana tests pass, lint clean.

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 automation 🚨 Merging this PR could break CI, automerge, proof capture, label sync, or automation. and removed 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. labels Jul 1, 2026
@ss251

ss251 commented Jul 1, 2026

Copy link
Copy Markdown
Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 1, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

clawsweeper's re-review flagged that the new NSTimeZone.default
override test lives in a non-serialized suite, unlike the existing
BedrockUsageStatsTests that uses the same pattern -- unrelated
parallel Swift Testing cases could observe UTC+14 and fail
nondeterministically. Mark @suite(.serialized), matching Bedrock's
precedent exactly.
@ss251

ss251 commented Jul 1, 2026

Copy link
Copy Markdown
Author

Addressed the P2 finding from the re-review: marked SakanaUsageFetcherTests @Suite(.serialized), matching the existing BedrockUsageStatsTests precedent for the same NSTimeZone.default mutation pattern. Confirmed serialized execution locally (tests now run strictly one-at-a-time instead of all starting concurrently). 12/12 pass, lint clean.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 1, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added 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. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 automation 🚨 Merging this PR could break CI, automerge, proof capture, label sync, or automation. labels Jul 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. 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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sakana AI: weekly/5-hour quota reset time is off by the viewer's UTC offset

1 participant