install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/zod-error-handling" ~/.claude/skills/intense-visions-harness-engineering-zod-error-handling && rm -rf "$T"
manifest:
agents/skills/claude-code/zod-error-handling/SKILL.mdsource content
Zod Error Handling
Handle Zod validation failures with safeParse, ZodError, error.format, error.flatten, and custom error maps
When to Use
- Returning field-level validation errors to the client (API responses, form feedback)
- Logging or monitoring validation failures without crashing
- Providing localized or application-specific error messages
- Building a validation error response shape consistent across your API
Instructions
- Use
instead of.safeParse()
whenever the caller needs to handle errors — it never throws:.parse()
import { z } from 'zod'; const UserSchema = z.object({ name: z.string().min(1), email: z.string().email(), age: z.number().int().min(0), }); const result = UserSchema.safeParse(rawInput); if (!result.success) { // result.error is a ZodError console.error(result.error.issues); return { errors: result.error.format() }; } // result.data is typed const user = result.data;
- Use
to produce a nested error object mirroring the schema shape:error.format()
const result = UserSchema.safeParse({ name: '', email: 'not-an-email', age: -1 }); if (!result.success) { const formatted = result.error.format(); /* { _errors: [], name: { _errors: ['String must contain at least 1 character(s)'] }, email: { _errors: ['Invalid email'] }, age: { _errors: ['Number must be greater than or equal to 0'] } } */ }
- Use
for a flat error object — simpler to consume in UI frameworks:error.flatten()
const flattened = result.error.flatten(); /* { formErrors: [], // top-level (non-field) errors fieldErrors: { name: ['String must contain at least 1 character(s)'], email: ['Invalid email'], age: ['Number must be greater than or equal to 0'] } } */ // Access field errors const nameErrors = flattened.fieldErrors.name ?? [];
- Inspect raw issues for fine-grained error handling:
import { z, ZodIssueCode } from 'zod'; const result = UserSchema.safeParse(rawInput); if (!result.success) { for (const issue of result.error.issues) { console.log(issue.path); // ['email'] console.log(issue.code); // 'invalid_string' console.log(issue.message); // 'Invalid email' } }
- Use
to override error messages globally (e.g., for i18n):z.setErrorMap()
import { z, ZodErrorMap, ZodIssueCode } from 'zod'; const customErrorMap: ZodErrorMap = (issue, ctx) => { if (issue.code === ZodIssueCode.invalid_type) { if (issue.expected === 'string') { return { message: 'This field requires a text value' }; } } if (issue.code === ZodIssueCode.too_small && issue.type === 'string') { return { message: `Must be at least ${issue.minimum} characters` }; } return { message: ctx.defaultError }; }; z.setErrorMap(customErrorMap);
- Pass a custom error map per-parse without setting it globally:
const result = UserSchema.safeParse(rawInput, { errorMap: (issue, ctx) => { if (issue.path[0] === 'email') { return { message: 'Please enter a valid email address' }; } return { message: ctx.defaultError }; }, });
- Build a reusable API error response from Zod errors:
function formatValidationError(error: z.ZodError): Record<string, string[]> { return error.flatten().fieldErrors as Record<string, string[]>; } // In a Next.js API route or server action: const result = CreateUserSchema.safeParse(await req.json()); if (!result.success) { return Response.json( { success: false, errors: formatValidationError(result.error) }, { status: 400 } ); }
Details
ZodError structure:
A
ZodError is an Error subclass with an issues array. Each issue has:
| Field | Type | Description |
|---|---|---|
| | The error category (e.g., , ) |
| | Path to the failing field |
| | Human-readable error message |
Checking for specific error types:
import { ZodError, ZodIssueCode } from 'zod'; function isValidationError(err: unknown): err is ZodError { return err instanceof ZodError; } function hasEmailError(err: ZodError): boolean { return err.issues.some( (issue) => issue.path.includes('email') && issue.code === ZodIssueCode.invalid_string ); }
safeParseAsync:
For schemas with async refinements or transforms, use
safeParseAsync():
const result = await UserSchema.safeParseAsync(rawInput);
Logging without sensitive data:
Before logging Zod errors, sanitize the input to avoid logging passwords or tokens:
if (!result.success) { logger.warn('Validation failed', { issues: result.error.issues.map((i) => ({ path: i.path, code: i.code, message: i.message })), // Do NOT log the raw input }); }
Source
https://zod.dev/error-handling
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.