Harness-engineering zod-error-handling

Zod Error Handling

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.md
source 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

  1. Use
    .safeParse()
    instead of
    .parse()
    whenever the caller needs to handle errors — it never throws:
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;
  1. Use
    error.format()
    to produce a nested error object mirroring the schema shape:
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'] }
  }
  */
}
  1. Use
    error.flatten()
    for a flat error object — simpler to consume in UI frameworks:
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 ?? [];
  1. 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'
  }
}
  1. Use
    z.setErrorMap()
    to override error messages globally (e.g., for i18n):
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);
  1. 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 };
  },
});
  1. 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:

FieldTypeDescription
code
ZodIssueCode
The error category (e.g.,
invalid_type
,
too_small
)
path
(string | number)[]
Path to the failing field
message
string
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

  1. Read the instructions and examples in this document.
  2. Apply the patterns to your implementation, adapting to your specific context.
  3. 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.