Skip to content

Creating Command Handlers ​

Error Handling ​

Application Errors ​

Throw an Error in your handler. The framework displays the message and exits with a non-zero status code:

typescript
handler: async ({ positional, options }) => {
  if (!fs.existsSync(positional)) {
    throw new Error(`File not found: ${positional}`);
  }
  // Your application logic
}

Best Practices ​

Don't Manually Validate Schema-Defined Inputs ​

Action: Let Zod validate inputs automatically—don't add manual validation for schema-defined options or positional arguments.

Why: The framework automatically validates all inputs against your Zod schemas and provides detailed, user-friendly error messages. Manual validation is redundant and can lead to inconsistent error messages.

typescript
{
  // ❌ Don't do this
  handler: ({ positional, options }) => {
    if (!positional) {
      throw new Error('Positional argument required');
    }
    if (typeof options.output !== 'string') {
      throw new Error('Output must be a string');
    }
    // ...
  }

  // ✅ Do this - let Zod handle it
  positional: yourPositionalSchema,
  options: yourOptionsSchema,
  handler: ({ positional, options }) => {
    // positional and options are already validated
    // ...
  }
}

Throw Descriptive Application Errors ​

Action: When throwing errors for application logic (file operations, network requests, business rules), include specific information in the error message.

Why: Descriptive error messages help users understand what went wrong and how to fix it. The framework will automatically display your error message, so make it actionable.

typescript
// ❌ Less helpful
throw new Error('Error');

// ✅ Better
throw new Error(`File not found: ${filePath}`);
throw new Error(`Cannot write to ${outputPath}: Permission denied`);
throw new Error(`Invalid file format: expected PDF, got ${actualFormat}`);

Use Try-Catch Only for Application Logic ​

Action: Only use try-catch blocks for application-specific error handling like cleanup, retries, or resource management. Always re-throw errors after handling.

Why: This allows you to perform necessary cleanup or recovery while still letting the framework display the error to the user.

typescript
handler: async ({ options }) => {
  let resource;
  try {
    resource = await acquireResource();
    await riskyOperation(resource);
  } catch (error) {
    // Cleanup before re-throwing
    if (resource) {
      await releaseResource(resource);
    }
    throw error; // Re-throw so framework can display it
  }
}

Throw Errors for Application Failures ​

Action: Simply throw an Error when application operations fail (file not found, permission denied, network errors, etc.). Don't implement custom error display logic.

Why: The framework automatically displays thrown errors and exits with the appropriate status code. This keeps your code focused on business logic rather than error presentation.

Framework Behavior ​

The framework automatically handles validation errors. See Error Handling Explanation for details.