Skip to content

tasks: window-start date depends on host $TZ, so TestTheManualQuotesBothTasksOpeningHeadingsExactly fails east of UTC #1808

Open

Description

@ursasi

What happened

On 2026-10-11, base dev@973b7d2, a deterministic unit test (no model) fails on a machine whose local timezone is east of UTC. The command and its output:

$ TZ=Asia/Shanghai go test ./internal/tui3/ -run TestTheManualQuotesBothTasksOpeningHeadingsExactly -count=1
--- FAIL: TestTheManualQuotesBothTasksOpeningHeadingsExactly (0.10s)
    tallyrows_test.go:244: the tasks page opens with "1 chat · 148 subtasks since aug 12 · $34.10", which the chat manual does not quote
FAIL

The chat manual quotes aug 11, not aug 12:

$ grep -n "subtasks since" internal/manual/chat/tasks.md
2641:`1 chat · 148 subtasks since aug 11 · $34.10`. The count is every row the time window

The test fixes now to 2026-08-25 13:11 in time.FixedZone("fixture", -4h) and builds a 15-day window with session.LastDays(now, 15). The window-start it renders depends on the host timezone, not the fixture zone, so the same source produces a different date on different machines — green on a UTC CI runner, red on a UTC+8 developer box.

Replication

Deterministic (no model). Runs at a reviewer's desk this minute; the only variable is $TZ:

# red — window start renders as "aug 12"
TZ=Asia/Shanghai go test ./internal/tui3/ -run TestTheManualQuotesBothTasksOpeningHeadingsExactly -count=1

# green — window start renders as "aug 11", which is what the manual quotes
TZ=UTC            go test ./internal/tui3/ -run TestTheManualQuotesBothTasksOpeningHeadingsExactly -count=1
TZ=America/New_York go test ./internal/tui3/ -run TestTheManualQuotesBothTasksOpeningHeadingsExactly -count=1

The band that passes is roughly the author's own: a fixture now of 2026-08-25 13:11 -04:00 stays on Aug 25 after converting to a local zone at or west of UTC, so the 15-day window starts Aug 11; converted into UTC+8 it rolls to Aug 26, and the window starts Aug 12.

Field (real models). None needed — the defect is a pure, deterministic rendering path with a fixed clock. The product-facing shadow of the same cause: on a host east of UTC the /tasks page header N subtasks since <date> (and the usage/spend window it shares) is anchored to the host's local calendar day rather than to the time it was handed, so "last 15 days" can start a day later than the window actually filtered on.

Where

  • TestTheManualQuotesBothTasksOpeningHeadingsExactly — internal/tui3/tallyrows_test.go (search subtasks since). It builds the heading with a fixed-zone now and asserts the chat manual quotes it verbatim.
  • tasksWindowStart — internal/tui3/tasksplace.go (search since / win.Normalized()), which formats win.From as Jan 2 lowercased.
  • LastDays and the root cause usageBucketStart — internal/session/usage_spend.go (search at = at.Local()). usageBucketStart truncates to midnight in at.Local(), i.e. the host timezone, discarding the location the passed-in time.Time already carries. LastDays builds its window off that, so the window start is host-timezone-dependent.
  • The quoted value — internal/manual/chat/tasks.md (search subtasks since), which hardcodes aug 11. Both tasks.md and the window reading were introduced together in senior-dev: read its window from codeaf offline, refuse tiny models, keep progress on compaction #1791.

The fix

The window start a given now produces should be a function of that now and nothing else, so the same inputs render the same date on every machine. Make usageBucketStart truncate in the given time's own location (at.Location()) rather than at.Local(): a function handed an explicit now time.Time should honor the zone that time already carries, and a caller that genuinely wants the host's wall-clock day can pass now.Local(). The test then reads the same aug 11 under every $TZ, and the manual's quoted aug 11 is correct as written.

(If instead the product deliberately wants the viewer's host-local calendar day, then the test is the thing that is wrong — it must construct now and assert in one explicit zone rather than leaking $TZ. Either way the current state is a test that is green only in the author's timezone, which is the bug.)

Acceptance

  • Unit: TestTheManualQuotesBothTasksOpeningHeadingsExactly renders the identical heading — 1 chat · 148 subtasks since aug 11 · $34.10 — under TZ=UTC, TZ=Asia/Shanghai and TZ=America/New_York, so the pass no longer depends on $TZ. (One deterministic test, no model: this behavior shows fully at the unit boundary, so no e2e door is involved.)
  • Unit: a direct check that session.LastDays(now, 15).From, for a now carrying a fixed non-UTC zone, is the same calendar day regardless of the host $TZ under which the test runs.
  • The manual page internal/manual/chat/tasks.md quotes the one deterministic value, and the change entry's invalidates names the belief that the tasks-page since <date> is host-timezone-independent today.
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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions