Skip to content

fix(web): validate signalId as a CUID in signals server functions - #3912

Draft
geclos wants to merge 1 commit into
developmentfrom
claude/trusting-allen-fpd67w
Draft

fix(web): validate signalId as a CUID in signals server functions#3912
geclos wants to merge 1 commit into
developmentfrom
claude/trusting-allen-fpd67w

Conversation

@geclos

@geclos geclos commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Fixes a production crash surfaced in Datadog Error Tracking: 596d8076-7a0c-11f1-8afa-da7ad0900005RepositoryError: Too large value for FixedString(24): value structured-output-json-parse-failure cannot be parsed as FixedString(24) for query parameter 'signalId' in web (GET, listSignalSessions).

Root cause: every input schema in apps/web/src/domains/signals/signals.functions.ts (~13 of them) validated signalId/signalIds as a bare z.string(), then cast it through the unchecked SignalId() helper ((value: string): SignalId => value as SignalId — a type-cast, not a validator) before passing it straight into ScoreAnalyticsRepository/ScoreRepository calls. Several of those repository methods (listSessionsBySignal, countSessionsBySignal, listTracesBySignal, countTracesBySignal, trendBySignal) query ClickHouse columns typed FixedString(24). Any malformed signalId — e.g. a stale/bad deep-link query param — skipped the signalIdSchema (CUID) validation already used everywhere else in the domain layer (packages/domain/signals/src/use-cases/{update-signal,delete-signal,apply-signal-lifecycle-command,list-signals}.ts) and reached the ClickHouse driver raw, throwing an unhandled RepositoryError instead of a clean 400.

This wasn't a one-off: the same unchecked z.string()SignalId() pattern appeared at every sibling call site in the file (getSignalRowMetrics, listSignalSessions, countSignalSessions, getSignalImpact, getSignalDimensions, getRelatedSignals, getSignalOccurrences, updateSignalTriage, applySignalLifecycleAction, updateSignal, updateSignalEvaluation, deleteSignal), so the fix replaces z.string()/z.array(z.string()) with the existing signalIdSchema/z.array(signalIdSchema) (cuidSchema.transform(SignalId)) across all of them, matching the convention already established in the domain layer. No behavior changes for well-formed IDs; malformed ones now fail fast with a validation error at the server-function boundary instead of a raw infra exception downstream.

Related issue (if applicable)

Datadog Error Tracking issue: https://app.datadoghq.eu/error-tracking/issue/596d8076-7a0c-11f1-8afa-da7ad0900005 (first seen 2026-07-07, low volume so far — new regression, not yet an open GitHub issue)

Closes #

How was this tested?

  • Added apps/web/src/domains/signals/signals.functions.test.ts, exercising signalTracesInputSchema (used by the crashing listSignalSessions function): accepts a well-formed CUID, rejects the exact malformed value from the Datadog error (structured-output-json-parse-failure) and an empty string.
  • Confirmed the new tests fail against the pre-fix schema (temporarily reverted signalId to z.string()) and pass with the fix restored.
  • pnpm --filter web typecheck (tsgo) — clean.
  • pnpm exec biome check on changed files — clean.
  • pnpm --filter web exec vitest run src/domains/signals — all 13 tests pass, no regressions in the existing signals test suite.

Verification in production: after deploy, occurrences of Datadog issue 596d8076-7a0c-11f1-8afa-da7ad0900005 should stop; any future malformed signalId will surface as a clean 400 validation error rather than an unhandled RepositoryError.

Checklist

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

Generated by Claude Code

Every signals.functions.ts input schema accepted signalId as a bare
z.string(), then cast it with the unchecked SignalId() helper before
passing it straight into ClickHouse-backed repository calls (several of
which query columns typed FixedString(24)). A malformed signalId (e.g. a
stale deep-link query param) skipped the signalIdSchema (CUID) validation
already used everywhere else in the domain layer and reached the
ClickHouse driver raw, surfacing as an unhandled RepositoryError instead
of a clean validation error.

Switch every signalId/signalIds field in this file to the existing
signalIdSchema (cuidSchema.transform(SignalId)), matching the convention
already used in packages/domain/signals use-cases.

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

vercel Bot commented Jul 7, 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 Jul 7, 2026 2:17pm

Request Review

@github-actions github-actions Bot added the Stale label Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Inbox

Development

Successfully merging this pull request may close these issues.

2 participants