Skip to content

fix(analytics): reject unresolvable gtePercentile filters on queryAnalytics - #4479

Draft
geclos wants to merge 1 commit into
developmentfrom
claude/busy-allen-5s04a2
Draft

fix(analytics): reject unresolvable gtePercentile filters on queryAnalytics#4479
geclos wants to merge 1 commit into
developmentfrom
claude/busy-allen-5s04a2

Conversation

@geclos

@geclos geclos commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Fixes an unhandled 500 in queryAnalytics (POST /v1/projects/:projectSlug/analytics/query) when a caller sends a gtePercentile filter on a field the stream can't resolve it for.

Datadog issue addressed: bb657964-773c-11f1-b715-da7ad0900005Error: Unsupported filter operator: gtePercentile (api, packages/platform/db-clickhouse/src/filter-builder.ts:201). Regressed on 2026-08-18 after a prior fix (#3839) had resolved a different call site; over the last 7 days this fired from POST /v1/projects/:projectSlug/analytics/query (stream: "sessions") via buildSessionFilterClauses (session-repository.ts:376).

Root cause: gtePercentile is only meaningful when the filter builder resolves it into an absolute threshold first:

  • traces/sessions streams resolve it, but only for duration/ttft/cost (resolvePercentileFilters) — any other field falls through unresolved.
  • scores/behaviors/moments streams never resolve it at all.

Either way, an unresolved gtePercentile reaches buildClause in filter-builder.ts, which throws a plain Error("Unsupported filter operator: gtePercentile") — an unhandled 500 instead of a 400.

This exact class of bug was already fixed for querySpans/analytics stream: "spans" (#3839, spanRowFilterSetSchema) and for the traces domain elsewhere (#4086, traceFilterSetSchema) — but queryAnalytics's own schema (analyticsQuerySchema in packages/domain/shared/src/analytics-query.ts) never adopted either hardened schema. All non-span streams shared one permissive filterSetSchema for filters, so the gap silently generalized to sessions (the one that fired in production), plus traces, scores, behaviors, and moments.

Fix:

  • packages/domain/shared/src/filter.ts: add sessionFilterSetSchema (mirrors traceFilterSetSchema, restricting gtePercentile to duration/ttft/cost) and a rejectGtePercentileFilterSetSchema(streamLabel) factory for streams with no percentile-resolution step at all.
  • packages/domain/shared/src/analytics-query.ts: give each stream variant of analyticsQuerySchema its own filters schema matching what its filter builder actually supports (traceFilterSetSchema for traces, sessionFilterSetSchema for sessions, existing spanRowFilterSetSchema for spans, rejectGtePercentileFilterSetSchema for scores/behaviors/moments) instead of one shared permissive schema.

Handler-side, packages/operations/src/operations/analytics.ts already re-validates the request body through analyticsQuerySchema before it reaches the filter builder, so this alone is enough to turn the crash into a clean 400 — no handler changes needed.

Related issue (if applicable)

N/A — found via Datadog Error Tracking triage, not a filed issue.

How was this tested?

Added regression tests to packages/domain/shared/src/analytics-query.test.ts:

  • gtePercentile on duration/ttft/cost still succeeds for traces and sessions.
  • gtePercentile on a non-percentile-eligible field (e.g. tokensInput) is now rejected for traces and sessions.
  • gtePercentile on any field is now rejected for scores, behaviors, and moments.

Confirmed the new tests fail without the fix (reproducing the exact unhandled-500 codepath) by temporarily stashing the filter.ts/analytics-query.ts changes and re-running — both new assertions failed as expected — then restored the fix and re-ran to green.

pnpm --filter @domain/shared test        # 225/225 passing
pnpm --filter @domain/shared typecheck    # clean (tsgo)
pnpm --filter @repo/operations typecheck  # clean (tsgo)
pnpm --filter @platform/db-clickhouse typecheck  # clean (tsgo)
pnpm exec biome check <changed files>     # clean

Verification in production: after deploy, occurrences of Datadog issue bb657964-773c-11f1-b715-da7ad0900005 should stop (any further gtePercentile misuse on these streams now returns a 400 from the queryAnalytics handler instead of a 500).

Remaining risk: the OpenAPI/SDK-facing mirror schema (AnalyticsQueryBodySchema in packages/operations/src/openapi/entities/analytics.ts) still documents filters with the generic, unrestricted shape for sessions/scores/behaviors/moments — its code comment notes it's a Fern-generator-friendly mirror and that "domain filter constraints are re-checked in the handler via the domain schema," which this PR relies on. The runtime behavior is now correct (400 instead of 500), but SDK-generated types/docs for these streams don't yet reflect the gtePercentile restriction. Left out of this PR to keep the fix minimal and low-risk; flagging as a good follow-up for API-docs accuracy.

Checklist

  • Lint, type-checking, and tests pass locally
  • PR title follows Conventional Commits
  • I have signed the CLA

Generated by Claude Code

…lytics

The trace/session/scores/behaviors/moments streams of queryAnalytics accepted
gtePercentile on any filter field, but only duration/ttft/cost are ever
resolved into absolute thresholds (traces/sessions) or supported at all
(scores/behaviors/moments). Any other field reached the ClickHouse filter
builder unresolved and threw a plain Error, surfacing as an unhandled 500
instead of a validation 400 — the same class of bug already fixed for
querySpans/analytics stream:"spans" filters (#3839) and the traces stream
elsewhere (#4086), but never applied to queryAnalytics's own schema.

Adds sessionFilterSetSchema (mirroring traceFilterSetSchema) and a
rejectGtePercentileFilterSetSchema factory for the streams with no
percentile-resolution step, and wires each queryAnalytics stream variant to
the schema matching its actual filter builder instead of the generic
filterSetSchema.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PoYZKJTkZD8NVBhpYkYfi1
@vercel

vercel Bot commented Aug 19, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
design-system Skipped Skipped Aug 19, 2026 9:30am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 02f661eb-9d93-4567-bae8-861897db96e8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Labels

None yet

Projects

Status: Inbox

Development

Successfully merging this pull request may close these issues.

2 participants