Skip to content

[superlog] Reject unrecognized timezones before passing to ClickHouse#488

Open
superlog-app[bot] wants to merge 1 commit into
stagingfrom
superlog/fix-unknown-timezone-clickhouse
Open

[superlog] Reject unrecognized timezones before passing to ClickHouse#488
superlog-app[bot] wants to merge 1 commit into
stagingfrom
superlog/fix-unknown-timezone-clickhouse

Conversation

@superlog-app

@superlog-app superlog-app Bot commented Jun 18, 2026

Copy link
Copy Markdown

Summary

Analytics dashboard queries fail with ClickHouse error code 36 (Cannot load time zone Etc/Unknown) when a user's browser reports an unrecognized timezone such as Etc/Unknown. The API passes the raw ?timezone= query parameter directly to ClickHouse without validating it against the IANA timezone database, so any unknown timezone name causes every query in the batch to fail, leaving the dashboard with no data even though the request returns HTTP 200.

The existing validateTimezone helper in @databuddy/validation only checks character format (/^[A-Za-z_/+-]{1,64}$/), which Etc/Unknown passes. It never verifies that the timezone is actually recognized by the runtime. The fix adds an Intl.DateTimeFormat probe inside validateTimezone so that any timezone the JS runtime rejects is also rejected by the helper. The /v1/query execute and compile handlers are updated to run validateTimezone on the incoming timezone param instead of using it raw, falling back to "UTC" when validation fails.

Alternative approach: validate at the ClickHouse query layer (e.g., in SimpleQueryBuilder.compile) and substitute UTC there. This patch chooses the earlier sanitization point so the fix benefits all call sites that use validateTimezone, not only the query route.

Incident on Superlog


Was this PR helpful? Leave feedback — goes straight to the Superlog team.


Summary by cubic

Reject unrecognized IANA timezones and default to UTC in query endpoints to prevent ClickHouse error 36 and broken analytics dashboards. Timezone input is now validated with Intl.DateTimeFormat via validateTimezone in @databuddy/validation.

  • Bug Fixes
    • Added runtime timezone check in validateTimezone; invalid names return an empty string.
    • /v1/query compile and execute now use validateTimezone(q.timezone) || "UTC".
    • Tests cover rejecting Etc/Unknown and America/Fakecity.

Written for commit 0ec3162. Summary will update on new commits.

Review in cubic

@vercel

vercel Bot commented Jun 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dashboard Ready Ready Preview, Comment Jun 18, 2026 12:30am
databuddy-status Ready Ready Preview, Comment Jun 18, 2026 12:30am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
documentation Skipped Skipped Jun 18, 2026 12:30am

@vercel vercel Bot temporarily deployed to Preview – documentation June 18, 2026 00:29 Inactive
@unkey-deploy

unkey-deploy Bot commented Jun 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Unkey Deploy

Name Status Preview Inspect Updated (UTC)
api (preview) Ready Visit Preview Inspect Jun 18, 2026 12:30am

@blacksmith-sh

blacksmith-sh Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Found 7 test failures on Blacksmith runners:

Failures

Test View Logs
[chromium] › test/e2e/specs/regressions/
api-keys.spec.ts:4:5 › creates and deletes an API key without leaving confirmation dial
ogs open @regression @core
View Logs
[chromium] › test/e2e/specs/regressions/
website-analytics.spec.ts:31:5 › shows seeded analytics data and applies a topbar filte
r @regression @core
View Logs
[chromium] › test/e2e/specs/regressions/
website-analytics.spec.ts:7:5 › renders analytics controls in the website topbar @regre
ssion @core
View Logs
[chromium] › test/e2e/specs/smoke/
account.spec.ts:3:5 › updates the signed-in user's profile name @smoke
View Logs
[chromium] › test/e2e/specs/smoke/
auth.spec.ts:12:5 › boots an authenticated browser session @smoke
View Logs
[chromium] › test/e2e/specs/smoke/
auth.spec.ts:30:5 › signs out and protects authenticated routes @smoke @core
View Logs
[chromium] › test/e2e/specs/smoke/
auth.spec.ts:3:5 › redirects unauthenticated visitors to sign in @smoke
View Logs

Fix in Cursor

@greptile-apps

greptile-apps Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a live incident where browsers reporting Etc/Unknown (or any unrecognized IANA timezone) caused every ClickHouse query in a batch to fail with error code 36, leaving dashboards empty. The existing validateTimezone helper only checked character format; it now also probes Intl.DateTimeFormat so that any timezone the JS runtime rejects is also rejected before reaching ClickHouse.

  • packages/validation/src/utilities.ts: Adds a new Intl.DateTimeFormat(\"en\", { timeZone: sanitized }) try/catch after the existing regex gate; returns \"\" on failure so callers fall back to UTC.
  • apps/api/src/routes/query.ts: Both the /v1/query compile and execute handlers are updated to pass q.timezone through validateTimezone instead of using it raw, with || \"UTC\" fallback.
  • packages/validation/src/utilities.test.ts: New assertions confirm Etc/Unknown and America/Fakecity are rejected.

Confidence Score: 4/5

The fix is targeted and correct — the Intl.DateTimeFormat probe reliably blocks the specific class of bad timezones that triggered the incident. The only rough edge is that rejected timezones are silently substituted with UTC, giving callers no signal that their parameter was discarded.

The core change is small and well-tested: adding an Intl.DateTimeFormat guard inside validateTimezone and threading it through both query handlers. The validation logic is sound, the tests cover the incident case directly, and the fallback is safe. The import placement is a style nit, and the silent UTC substitution is an observability gap rather than a correctness issue — but callers sending an invalid timezone will silently receive UTC-bucketed data with no indication anything went wrong.

apps/api/src/routes/query.ts — the import ordering and the silent UTC fallback with no log are both in this file.

Important Files Changed

Filename Overview
packages/validation/src/utilities.ts Adds an Intl.DateTimeFormat probe inside validateTimezone to reject timezones unrecognized by the JS runtime; clean implementation, correctly ordered after the regex gate.
packages/validation/src/utilities.test.ts Adds two new assertions for unrecognized IANA names (Etc/Unknown, America/Fakecity); covers the incident case directly and fits the existing test structure.
apps/api/src/routes/query.ts Both compile and execute handlers now call validateTimezone before passing timezone to ClickHouse; import is placed out of order, and the UTC fallback is completely silent.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Browser
    participant API as /v1/query (Elysia)
    participant Validator as validateTimezone()
    participant CH as ClickHouse

    Browser->>API: "POST ?timezone=Etc/Unknown"
    API->>Validator: validateTimezone("Etc/Unknown")
    Note over Validator: regex passes (chars ok)<br/>Intl.DateTimeFormat throws RangeError
    Validator-->>API: "" (empty string)
    API->>API: "" || "UTC" → "UTC"
    API->>CH: "query with timeZone="UTC""
    CH-->>API: results
    API-->>Browser: HTTP 200 (UTC data, no error signal)

    Browser->>API: "POST ?timezone=America/New_York"
    API->>Validator: validateTimezone("America/New_York")
    Note over Validator: regex passes<br/>Intl.DateTimeFormat succeeds
    Validator-->>API: "America/New_York"
    API->>CH: "query with timeZone="America/New_York""
    CH-->>API: results
    API-->>Browser: HTTP 200 (correct data)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Browser
    participant API as /v1/query (Elysia)
    participant Validator as validateTimezone()
    participant CH as ClickHouse

    Browser->>API: "POST ?timezone=Etc/Unknown"
    API->>Validator: validateTimezone("Etc/Unknown")
    Note over Validator: regex passes (chars ok)<br/>Intl.DateTimeFormat throws RangeError
    Validator-->>API: "" (empty string)
    API->>API: "" || "UTC" → "UTC"
    API->>CH: "query with timeZone="UTC""
    CH-->>API: results
    API-->>Browser: HTTP 200 (UTC data, no error signal)

    Browser->>API: "POST ?timezone=America/New_York"
    API->>Validator: validateTimezone("America/New_York")
    Note over Validator: regex passes<br/>Intl.DateTimeFormat succeeds
    Validator-->>API: "America/New_York"
    API->>CH: "query with timeZone="America/New_York""
    CH-->>API: results
    API-->>Browser: HTTP 200 (correct data)
Loading

Reviews (1): Last reviewed commit: "[superlog] Reject unrecognized timezones..." | Re-trigger Greptile

(async () => {
const requestId = generateRequestId();
const timezone = q.timezone || "UTC";
const timezone = validateTimezone(q.timezone) || "UTC";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Silent UTC fallback is invisible to callers

When validateTimezone rejects a timezone (returns ""), both the execute and compile paths silently substitute "UTC" and return HTTP 200. The caller has no programmatic signal that their ?timezone= parameter was discarded — they receive data bucketed in UTC without any indication. If a user's browser reports an unusual-but-passing-regex timezone that is rejected by Intl, their dashboard will silently display UTC times. Adding at minimum a server-side warning log (and ideally a response header such as X-Applied-Timezone: UTC) would make these silent substitutions visible during incident debugging.

Comment on lines 10 to 12
import { db } from "@databuddy/db";
import { validateTimezone } from "@databuddy/validation";
import { readBooleanEnv } from "@databuddy/env/boolean";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Import placed out of alphabetical order

The new @databuddy/validation import (package initial v) is inserted between @databuddy/db (d) and @databuddy/env/boolean (e). It should appear after the @databuddy/shared/… imports. If Ultracite/Biome's useSortedImports rule is active, this will produce a lint error.

Suggested change
import { db } from "@databuddy/db";
import { validateTimezone } from "@databuddy/validation";
import { readBooleanEnv } from "@databuddy/env/boolean";
import { db } from "@databuddy/db";
import { readBooleanEnv } from "@databuddy/env/boolean";

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants