Skip to content

refactor(cli): migrate self-contained runtime validation from zod to valibot - #1370

Merged
jared-outpost[bot] merged 2 commits into
mainfrom
issue-1367-zod-to-valibot
Aug 6, 2026
Merged

refactor(cli): migrate self-contained runtime validation from zod to valibot#1370
jared-outpost[bot] merged 2 commits into
mainfrom
issue-1367-zod-to-valibot

Conversation

@jared-outpost

@jared-outpost jared-outpost Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Migrates the CLI's self-contained runtime validation from zod to valibot to reduce startup overhead, per #1367.

what changed

Converted the validation that is not coupled to zod:

  • src/types/oauth.ts + src/lib/oauth.ts — OAuth device-flow response validation (device code, token, token error). .passthrough()looseObject; safeParse result access updated to valibot's .output / .issues.
  • src/types/config.ts — the config file schema (SentryConfig and friends).
  • src/lib/dsn/types.tsResolvedProjectInfoSchema and CachedDsnEntrySchema (DSN cache validation). z.enumpicklist.
  • src/lib/qrcode.ts — QR code options. z.boolean().default(true)optional(boolean(), true).
  • Added valibot as a dependency; used named (tree-shakeable) imports to satisfy biome's noNamespaceImport.

what stays on zod (and why)

Some of the CLI's zod usage can't be swapped in isolation, so it intentionally stays on zod for now:

  • @sentry/api/zod couplingsrc/types/sentry.ts, src/types/feedback.ts, and src/lib/api/replays.ts compose/consume auto-generated zod schemas from the external @sentry/api package (.pick/.partial/.extend/.shape, safeParseAsync). These can't move without upstream valibot schemas or an interop layer.
  • central response validatorsrc/lib/api/infrastructure.ts validates every API response via apiRequestToRegion(schema) and reads ZodError.issues for Sentry telemetry. Every src/lib/api/* schema flows through it, so migrating it is a repo-wide change with telemetry implications.
  • zod-internals introspectionsrc/lib/formatters/output.ts walks zod internals (_def.typeName) to generate --help / skill docs. Valibot's internal shape differs and would need a separate introspection implementation.

These are good candidates for follow-up PRs.

testing

  • vitest run on the affected suites (oauth types + lib, config, dsn) — 69 tests pass.
  • biome check on the changed files — clean.
  • tsc --noEmit — no new type errors introduced by these files (the only tsc errors are pre-existing, from generated files that require the codegen step).

Closes #1367

…valibot

Switches the CLI's uncoupled runtime validation to valibot to reduce
startup overhead: OAuth device-flow responses (types/oauth.ts +
lib/oauth.ts), the config file schema (types/config.ts), the DSN cache
schemas (lib/dsn/types.ts), and qrcode options (lib/qrcode.ts).

zod is retained for the schemas that are coupled to it and cannot be
migrated in isolation:
- types/sentry.ts, types/feedback.ts, lib/api/replays.ts compose/consume
  auto-generated schemas from @sentry/api/zod (external, zod-based)
- lib/api/infrastructure.ts is the central response validator every
  lib/api/* schema flows through and reads ZodError.issues for telemetry
- lib/formatters/output.ts introspects zod internals for --help/skill gen

Refs #1367
@jared-outpost

jared-outpost Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

fix-ci: attempt 1 — check:deps flagged valibot in dependencies. this package bundles everything at build time, so all packages must live in devDependencies (that's where zod already is). moving valibot over.

check:deps enforces that all packages live in devDependencies since the
CLI bundles everything at build time. valibot was added to dependencies;
move it to devDependencies alongside zod.
@jared-outpost

jared-outpost Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

fixed — moved valibot from dependencies to devDependencies (601c8ec). check:deps passes locally now:

✓ No runtime dependencies in package.json

the CLI bundles all packages at build time, so nothing should sit in dependencies — valibot now lives alongside zod in devDependencies. re-running CI.

@jared-outpost
jared-outpost Bot marked this pull request as ready for review August 5, 2026 20:46
@jared-outpost
jared-outpost Bot requested a review from BYK August 5, 2026 20:46
@jared-outpost

jared-outpost Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

CI is green — marking this ready for review. the Build workflow passed (Lint & Typecheck incl. check:deps, Unit Tests, Validate generated files), plus Warden, Secret Scan, Dependency Review, and Semgrep. only the org-managed Socket Security PR scan is still settling, which doesn't gate.

self-review's clean: the valibot migration is scoped to the uncoupled validation (OAuth, config, DSN cache, qrcode) and the rest stays on zod as documented in the description.

@github-actions github-actions Bot added the risk: high PR risk score: high label Aug 5, 2026
@BYK

BYK commented Aug 6, 2026

Copy link
Copy Markdown
Member

Okay Jared, ship this and file a follow up to remove Zod completely

@jared-outpost
jared-outpost Bot merged commit 0348cda into main Aug 6, 2026
32 checks passed
@jared-outpost
jared-outpost Bot deleted the issue-1367-zod-to-valibot branch August 6, 2026 12:03
@jared-outpost

jared-outpost Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

shipped + follow-up at #1371

jared-outpost Bot added a commit that referenced this pull request Aug 7, 2026
---
first pass on #1371 — removing the remaining zod usage after the valibot
migration (#1370).

this PR does the one piece that can be migrated in isolation, plus the
dependency bump that unblocks the rest.

## what changed
- **bump `@sentry/api` `^0.253.0` → `^0.256.0`.** required: 0.256.0 is
the first release that actually ships the `./valibot` entrypoint —
0.253–0.255 advertise it in their `exports` map but ship no
`valibot.js`.
- **`lib/api/replays.ts`**: the SDK `responseValidator` now uses
`vListProjectReplayRecordingSegmentsResponse` from `@sentry/api/valibot`
+ valibot `safeParse`, instead of
`zListProjectReplayRecordingSegmentsResponse.safeParseAsync` from
`@sentry/api/zod`. this is the only zod usage not coupled to the two
shared hubs (see below), so it's the only piece safely migratable on its
own.
- **`types/sentry.ts`**: the SDK bump narrows
`GetOrganizationIssueResponse["status"]`, which made the
`ISSUE_STATUSES` `satisfies` drift-guard misfire. relaxed it to a
deliberate CLI superset that keeps `resolvedInNextRelease` and `muted` —
both are still emitted by the retrieve-issue endpoint and still rendered
by the CLI (`STATUS_ICONS`/`STATUS_LABELS`/`STATUS_COLORS`). **no
rendering behavior changes.**
- regenerated skill reference docs (`event.md`, `issue.md`) — the SDK
bump flipped `metadata` nullability; committed to keep the
`check-generated` CI job green.

## tested
- `tsc --noEmit`: clean
- `biome check` on changed files: clean
- `vitest run test/lib/api/replays test/types/sentry
test/lib/formatters`: 1001 passed

## follow-ups (remaining zod, tracked in #1371)
the rest can't be split cleanly because two shared hubs force an
all-or-nothing migration of the schemas that flow through them:
- **`lib/api/infrastructure.ts`** — `schema?: z.ZodType<T>` +
`.safeParse()` is used by ~30 callsites across the api layer; changing
the type migrates them all at once (valibot's `safeParse` is a free
function, not a method).
- **`lib/formatters/output.ts`** —
`extractSchemaFields`/`zodTypeToString` read zod internals
(`_def.typeName`, `.shape`, union `.options`) for ~16 commands'
`--help`/`--fields` docs; valibot's runtime shape
(`.type`/`.entries`/`.wrapped`) is different and needs a rewrite.
- the `@sentry/api/zod` schemas in `types/sentry.ts` +
`types/feedback.ts` (`zBaseTeam`, `zGetOrganizationIssueResponse`,
`zGroupEventsResponseDict`, `zEventAttachmentDetailsResponse`) → their
`v*` equivalents, incl. reworking the
`.pick`/`.partial`/`.extend`/`.shape`/`.describe` derivations.
- the ~13 self-contained `z.object` schemas (conversation, dashboard,
replay, seer, proguard, code-mappings, chunk-upload, dart-symbols,
debug-files, preprod-artifacts, conversations, dashboards) + the
`zod_validation` telemetry paths in `infrastructure.ts`/`logs.ts`.

once all of the above land, the `zod` dependency can be dropped
entirely.

⚠️ maintainer note: the `@sentry/api` 0.256 status-union narrowing is a
behavior-adjacent change — see the inline comment on `ISSUE_STATUSES`.
---

---------

Co-authored-by: jared-outpost[bot] <jared-outpost[bot]@users.noreply.github.com>
jared-outpost Bot added a commit that referenced this pull request Aug 7, 2026
Completes the zod→valibot migration started in #1370/#1388 and drops the
`zod` dependency entirely.

Migrated the three coupling categories from #1371:
- `@sentry/api/zod` schemas in types/sentry.ts, types/feedback.ts →
  `@sentry/api/valibot` (v*-prefixed), reworking the .pick/.partial/.shape
  derivations (valibot has no .extend — composed via object spread).
- Central response validator in lib/api/infrastructure.ts + lib/api/logs.ts:
  z.ZodType → GenericSchema, schema.safeParse → safeParse(schema, data),
  result.error.issues → result.issues. Sentry telemetry context renamed
  zod_validation → schema_validation.
- Zod-internals introspection in lib/formatters/output.ts rewritten for
  valibot's runtime shape (.entries/.wrapped/.options + getDescription),
  incl. pipe/coercion type resolution and nested-wrapper descriptions.

Also migrated the ~13 self-contained z.object schema files (types/*,
lib/api/*), commands/code-mappings/upload.ts, and the test suites' schema
API usage.

Verified: tsc clean, biome clean, generated skill docs regenerate
unchanged (introspection parity), full lib/types/commands test suites pass.

Closes #1371
BYK pushed a commit that referenced this pull request Aug 7, 2026
finishes the zod→valibot migration from #1370/#1388 and drops the `zod`
dependency entirely. covers all three coupling categories in #1371 plus
the ~13 self-contained schema files that were still on zod.

## what changed
- **`@sentry/api/zod` → `@sentry/api/valibot`** (types/sentry.ts,
types/feedback.ts): switched to the `v*`-prefixed SDK schemas and
reworked the `.pick`/`.partial`/`.shape`/`.element` derivations. valibot
has no `.extend`, so extensions are composed via `object({
...Base.entries, ... })`.
- **central response validator** (lib/api/infrastructure.ts +
lib/api/logs.ts): `z.ZodType<T>` → `GenericSchema<unknown, T>`,
`schema.safeParse(x)` → `safeParse(schema, x)`,
`result.data`/`result.error.issues` → `result.output`/`result.issues`.
the Sentry telemetry context key is renamed `zod_validation` →
`schema_validation` (⚠️ see note below).
- **zod-internals introspection** (lib/formatters/output.ts):
`extractSchemaFields`/`zodTypeToString` read zod's
`_def.typeName`/`.shape`; rewritten against valibot's runtime shape
(`.entries`/`.wrapped`/`.options` + `getDescription()`), including
pipe/coercion type resolution and descriptions nested under wrapper
schemas.
- migrated the remaining self-contained `z.object` schemas
(types/conversation, dashboard, replay, seer; lib/api/proguard,
code-mappings, chunk-upload, dart-symbols, debug-files,
preprod-artifacts, conversations, dashboards),
`commands/code-mappings/upload.ts`, and the affected test suites' schema
API usage.
- **dropped `zod` from `packages/cli/package.json`** + refreshed the
lockfile.

## testing
- `tsc --noEmit`: clean
- `biome check ./src ./test`: clean (933 files)
- generated skill docs regenerate **unchanged** — confirms the
introspection rewrite is output-identical to the zod version
- full `test/lib`, `test/types`, `test/commands` suites pass (1800+
tests)

## notes for reviewers
- **telemetry key rename** `zod_validation` → `schema_validation` in
Sentry `setContext` — if any dashboards/alerts key off `zod_validation`,
they'll need updating.
- **`@sentry/api` zod peer**: `@sentry/api@0.256.0` still declares an
*optional* `zod: ^3.24.0` peer. with the CLI's direct zod dep removed,
transitive zod (from AI SDKs) floats to v4, so pnpm prints a benign
unmet-optional-peer warning. install and `--frozen-lockfile` both
succeed; our usage goes through the valibot entrypoint so zod isn't
needed at runtime.
- caught + fixed two migration regressions during self-review via the
generated-docs oracle: a dropped `nullable` on
`AIConversationDetailsSchema.title`, and `ReplayViewOutputSchema`
accidentally spreading the list-item base instead of the details schema
(dropping `clicks`/`replay_type`).

Closes #1371

<!--
## Plan
Migration executed in dependency-ordered waves with tsc + generated-docs
as the regression oracle:
1. Bump already merged (#1388): @sentry/api ^0.256.0 (first version
shipping the ./valibot entrypoint file).
2. Mechanical schema translation (delegated, verified) using a fixed
contract:
z.object->object, .passthrough()->looseObject,
.describe->pipe(x,description), .optional->optional,
.nullable->nullable, .nullish->nullish, .default(d)->optional(x,d),
.catch(d)->fallback(x,d),
z.enum->picklist, z.union->union, z.discriminatedUnion->variant,
z.coerce.number()->pipe(unknown(),transform(Number),number()),
.brand->pipe(x,brand()),
.pick/.partial/.omit->pick/partial/omit(Schema,keys),
.shape.x->.entries.x,
.element->.item, .extend->object({...Base.entries,...}),
z.infer->InferOutput, z.ZodType<T>->GenericSchema<unknown,T>.
safeParse result: .success/.output/.issues (no .error). issue path via
getDotPath.
type-only imports (InferOutput/GenericSchema) MUST use the type modifier
or they throw at runtime.
3. Hubs done by hand: infrastructure.ts + logs.ts validators; output.ts
introspection rewrite
(unwrap pipe to last kind==="schema" step for coercion; recurse .wrapped
for descriptions).
4. Semantic audit: compared per-file
nullable/optional/passthrough/nullish/catch counts old vs new; all
reconcile.
5. Verification: codegen leaves generated docs unchanged; tsc/biome
clean; test suites pass; drop zod dep.
-->

---------

Co-authored-by: jared-outpost[bot] <jared-outpost[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: high PR risk score: high

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Switch CLI from Zod to Valibot

1 participant