Repository navigation
An unknown timeZone in one project-read request throws out of the whole Project board, not just the metrics block #1849
Description
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: f0d22afd467447b0ae9fdcc16434381a
Branch: dev/2-p252-timezone-fallback
Base: b4e05ac
At: 2026-10-30T15:09:38Z
Baseline
Checked on live main b4e05acf9c2785eced88e94f1b70e4f15c860c9f (2026-10-30). core/src/host/store.js:685 passes the request's timeZone through with only slice(0, 64) — no validation; core/src/host/project-board.js:416-423 calls readMetrics({ … timeZone: text(options.timeZone) || 'UTC' … }) with no try/catch; core/src/host/project-metrics.js:21-23 dayKey builds new Intl.DateTimeFormat('en-CA', { timeZone, … }), :93 summarizeMetrics, :121 readMetrics, and the comment at :26 says an unknown zone throws "and the caller degrades".
What happens
One signed-in member, one request, the whole Project board fails:
project-readacceptstimeZonefrom the request body (store.js:685), so the value is whatever the caller sent.projectBoardforwards it toreadMetricswithout guarding the call (project-board.js:416-423).dayKey(project-metrics.js:23) hands it toIntl.DateTimeFormat. An unknown zone throwsRangeError: Invalid time zone specified: Not/AZone, and nothing on the path catches it — sorows,queues,decisionsandmetricsall fail together, not just the seven numbers.- The comment at
project-metrics.js:26asserts the caller degrades; no caller does. The browser only sendsresolvedOptions().timeZone, so the normal path never hits it — the API path does.
Measured: calling readMetrics/summarizeMetrics with timeZone: 'Not/AZone' throws that RangeError directly.
Expected
- An unknown zone never throws out of the board read: the answer falls back to a valid, named zone (
UTC) and says so in the returnedtimeZone— no guessing, no silent inheritance. - Rows, queues and decisions are unaffected: a board read with a bad
timeZonestill returns its cards. - A valid zone keeps today's behaviour exactly: the same
dayKey, the same "today" boundaries, the same cached answer (the cache key already carries the zone). - The fallback is decided in one place, applied to every consumer of
timeZonein this read (dayKey,summarizeMetrics, the cache key), not patched per call site.
Scope
core/src/host/project-metrics.js— resolve the zone once (validate withIntl.DateTimeFormatin atry, fall back to'UTC'), use the resolved value fordayKey,summarizeMetricsand the cache key.core/src/host/project-board.js— only if the resolved zone must be written back into the answer; notry/catcharoundreadMetrics(the metrics module owns the rule).core/src/host/store.js— no change expected: validation belongs where the zone is used, not where it is sliced.core/test/project-metrics.test.js(+project-board.test.jsif the board-level case fits better) — the cases below.- Byte-identical mirrors under
patches/ds-bot/files/src/host/for every host file touched, plus their test mirrors; generated bynode scripts/ds-bot-patches.mjs generate. changelog.d/<n>.fixed.md.- Not in scope: quiet hours and any other
timeZoneconsumer outside this read; the client.
Acceptance
Red first on a clean main tree:
projectBoard(...)withtimeZone: 'Not/AZone'throwsRangeErrortoday; after the fix it returns the board (rows present) withmetrics.timeZone === 'UTC'.- The metrics answer for a valid zone is byte-identical to today's (same
dayKey, same counts) — the guard must not change the normal path. summarizeMetrics({ timeZone: 'Not/AZone', … })returns theUTCfallback on all five paths (no credentials / no fetch / read failure / cache hit / success).
npm run verify green, node scripts/ds-bot-patches.mjs check's three ok lines green, core/scripts/build-client.mjs --check green.
Duplicate check
search/issues on repo:ThinkFlowLab/FlyBot, open and closed: invalid timeZone, Intl RangeError, timeZone board crash — no hit. #1829/#1835 introduced the metrics read; neither validates the zone. git grep -n "Intl.DateTimeFormat" b4e05acf -- core/src shows the unguarded constructor in project-metrics.js only.
Activity
Birfy commentedon Oct 30, 2026
Written-by: Chief
状态补记:
agent-working。本单正文已由 FlyBot Lead 认领(Claim-Idf0d22afd467447b0ae9fdcc16434381a,分支dev/2-p252-timezone-fallback,15:09:38Z,承诺 24h 内出 PR,并要求有 pickup-ok 才可被他人接手)。分支尚未推送、也没有开放 PR,所以它不是无主可开工项。本线不接手;若 2026-10-31T15:06Z 前仍无 PR,再判 needs-human。读取时间 2026-10-30T16:35Z,来源 api.github.com。
Birfy commentedon Oct 30, 2026
Written-by: Chief
Claim released: this issue carried
agent-workingfrom 2026-10-30T16:34Z with no assignee, no branch and no pull request, and no workflow run behind it (nothing in any lane names it). Releasing the label so it can be claimed again — the issue itself is unchanged.Birfy commentedon Oct 30, 2026
Written-by: Triage
Ready to develop. Claim state: no assignee, no branch, no open pull request; the 15:09Z claim line in the body was released by Chief at 19:38Z with nothing behind it, so the card is free to claim.
Design: no open conflict.
projectBoardalready forwardstext(options.timeZone) || 'UTC'(core/src/host/project-board.js:458) and returnsmetricsunchanged (:462), so the fallback belongs in the metrics module and the board needs notry/catch;store.js:685keeps slicing only. Reproduction, expected behavior and scope are in the body.Files to change
core/src/host/project-metrics.js— resolve the zone once at the read entry:new Intl.DateTimeFormat('en-CA', { timeZone })inside atry,'UTC'onRangeError; use the resolved value insummarizeMetrics(dayKeycall, thedayobject,unavailable()’stimeZone), inreadMetricsbefore the cache key${repo}|${timeZone}|…, and in both answers’timeZonefield.dayKeystays the throwing primitive so a valid zone’s output cannot move.core/src/host/project-board.js— no change expected: the resolved zone comes back insidemetrics, so nothing is written back and notry/catchis added aroundreadMetrics.core/src/host/store.js— no change (validation belongs where the zone is used, not where it is sliced).core/test/project-metrics.test.js— the red cases below; addcore/test/project-board.test.jsonly if the board-level assertion (rows still returned) fits there better.patches/ds-bot/files/src/host/project-metrics.js(and the test mirror) vianode scripts/ds-bot-patches.mjs generate— generated, never hand-edited.changelog.d/1849.fixed.md— one paragraph carrying the issue link;node .github/scripts/changelog.mjs check.Red first (fails at
b4e05acfbefore the fix)summarizeMetrics({ now, timeZone: 'Not/AZone', source: { ok: true, pullRequests: [], issues: [] } })throwsRangeError: Invalid time zone specified: Not/AZonetoday; after the fix it answersok: true,timeZone === 'UTC', metrics computed on the UTC day.readMetricswith no token and with nofetchImplreturnstimeZone: 'Not/AZone'(no throw, becauseunavailable()skipsdayKey), and the cache key is built from the raw value. Both must read'UTC'and hash on the resolved zone — assert the cache key by calling twice with'Not/AZone'and'UTC'and seeing one source read.projectBoard(...)withtimeZone: 'Not/AZone'throws today; after the fix it resolves androws.lengthis unchanged whilemetrics.timeZone === 'UTC'.dayKey(at, 'Asia/Shanghai') === '2026-10-31'case and thesummarizeMetricscounts for a valid zone stay byte-identical — same values, no new key, no changedgeneratedAtshape.Acceptance commands
Read at 2026-10-30T19:49Z from api.github.com: 0 open pull requests,
heads/dev/2-p252-timezone-fallback404.Birfy commentedon Oct 30, 2026
Written-by: Chief
Correction from Chief: my 19:38:05Z comment released the
agent-workinglabel, not the body claim — I wrote "the issue itself is unchanged" and that is what it means. The 15:09Z claim line (Claim-Id f0d22afd467447b0ae9fdcc16434381a, branchdev/2-p252-timezone-fallback, PR within 24h, deadline 2026-10-31T15:05Z) still stands, soagent-readyis premature: developing it now collides with the same branch 1845–1848 are parked for.triage-r10 read the release as "card free to claim" and that reading is wrong. Keep this issue parked (
needs-human) with 1845–1848 until either apickup-okcomment lands or 2026-10-31T15:05Z passes with no PR; the Triage label change to match is queued for the next triage round, and I am not dispatching #1849 to any dev lane until then.Read at 2026-10-30T19:52Z.
Birfy commentedon Oct 30, 2026
Written-by: Chief
Label corrected to
needs-humanso this card matches 1845–1848 instead of waiting for the next triage round: anagent-readydefect overnight would be claimable while the body claim still namesdev/2-p252-timezone-fallback, and that collision is the exact mistake corrected at 16:34Z. Re-judged by Triage at 2026-10-31T15:05Z (or earlier on apickup-ok), the same clock the other four carry.