Crust logoCrust

Error Handling

Handle errors with typed error codes and structured details.

Crust uses a custom CrustError class with typed error codes for all framework-level errors. This lets you handle errors programmatically without parsing error messages.

CrustError

Every error thrown by Crust is an instance of CrustError:

import { CrustError, parseArgs } from "@crustjs/core";

try {
  parseArgs(cmd._node, ["--unknown"]);
} catch (error) {
  if (error instanceof CrustError) {
    console.log(error.code);    // "PARSE"
    console.log(error.message); // 'Unknown flag "--unknown"'
  }
}

Error Codes

Crust defines five error codes — DEFINITION, VALIDATION, PARSE, EXECUTION, and COMMAND_NOT_FOUND. See the CrustError reference for when each is thrown.

Type Narrowing with .is()

Use .is() to narrow the error type:

if (error instanceof CrustError) {
  if (error.is("COMMAND_NOT_FOUND")) {
    // error.details is now typed as CommandNotFoundErrorDetails
    console.log(`Unknown: ${error.details.input}`);
    console.log(`Available: ${error.details.available.join(", ")}`);
  }

  if (error.is("VALIDATION")) {
    console.log("Missing required input:", error.message);
  }
}

COMMAND_NOT_FOUND Details

The COMMAND_NOT_FOUND error includes structured details for building helpful error messages:

interface CommandNotFoundErrorDetails {
  input: string;              // What the user typed
  available: string[];         // Valid subcommand names
  commandPath: string[];       // Path to the parent command
  parentCommand: CommandNode;  // The parent command node
}

The Autocomplete Plugin uses these details to suggest corrections.

Error Chaining with .withCause()

Chain an original error for debugging:

try {
  await someOperation();
} catch (originalError) {
  throw new CrustError("EXECUTION", "Operation failed").withCause(originalError);
}

Access the original via error.cause.

How .execute() Handles Errors

.execute() catches all errors, prints them to stderr as Error: <message>, and sets process.exitCode = 1. It never throws. This handles:

  • Validation errors (missing args/flags)
  • Parse errors (unknown flags, bad types)
  • Command not found errors
  • Runtime errors (exceptions in command handlers)
  • Non-CrustError exceptions are wrapped in CrustError("EXECUTION", ...) with the original error set as the cause

Error Wrapping

When a non-CrustError is thrown inside a command handler, .execute() automatically wraps it:

// Original: throw new Error("oops")
// Becomes: CrustError("EXECUTION", "oops") with cause set to original error

This ensures consistent error formatting for all errors.

Throwing Errors in Commands

For framework-level errors, throw CrustError:

import { CrustError } from "@crustjs/core";

.run(({ flags }) => {
  if (!isValidConfig(flags.config)) {
    throw new CrustError("VALIDATION", `Invalid config file: ${flags.config}`);
  }
})

For application-level errors, throw plain Error:

.run(({ flags }) => {
  const result = await deploy(flags.env);
  if (!result.ok) {
    throw new Error(`Deployment failed: ${result.message}`);
  }
})

Both are handled correctly — plain errors get wrapped in CrustError("EXECUTION", ...) automatically.

Validation Layers

Crust catches command definition mistakes through three complementary layers. Each layer guards against the same class of issues, so errors are caught as early as possible — at compile time if types are narrowed, at definition time if the builder is called, and at build time before a binary is shipped.

1. Compile-Time (TypeScript Types)

When you use .flags() and .args() with literal types, TypeScript catches structural issues before your code runs. The error appears as a red squiggle on the exact offending arg or flag.

CheckType UtilityBranded Error PropertyExample
Only last arg can be variadicValidateVariadicArgsFIX_VARIADIC_POSITION{ name: "files", variadic: true } as a non-last arg
No alias collisionsValidateFlagAliasesFIX_ALIAS_COLLISIONTwo flags both use short: "v"
No no- prefix on names/aliasesValidateNoPrefixedFlagsFIX_NO_PREFIXFlag named no-cache or alias no-store

These checks are bypassed when types are widened (e.g. as any, dynamic construction, or non-const generics). The runtime layers below serve as defense-in-depth.

2. Runtime (Builder Methods + parseArgs)

Even without TypeScript or when types are erased, Crust validates definitions at runtime:

.flags() throws CrustError("DEFINITION"):

CheckError Message
Flag name starts with no-Flag "--no-cache" must not use "no-" prefix; define "cache" and negate with "--no-cache"
Alias starts with no-Alias "--no-store" on "--cache" must not use "no-" prefix (reserved for negation)

Constructor throws CrustError("DEFINITION"):

CheckError Message
Empty namemeta.name must be a non-empty string

parseArgs throws CrustError("DEFINITION") or CrustError("PARSE"):

CheckCodeError Message
Alias collision (alias->name or alias->alias)DEFINITIONAlias collision: "-v" is used by both "--verbose" and "--version"
no- prefixed flag name (defense-in-depth)DEFINITIONFlag "--no-cache" must not use "no-" prefix...
no- prefixed alias (defense-in-depth)DEFINITIONAlias "--no-store" on "--cache" must not use "no-" prefix...
Unknown flag (strict mode)PARSEUnknown flag "--unknown"
Invalid number coercionPARSEExpected number for --port, got "abc"
Boolean value assignment (--flag=true)PARSEFailed to parse command arguments
Negating an alias (--no-loud)PARSECannot negate alias "--no-loud"; use "--no-verbose" instead

validateParsed throws CrustError("VALIDATION"):

CheckError Message
Missing required argumentMissing required argument "<name>"
Missing required flagMissing required flag "--config"

validateParsed is called after middleware, so plugins like helpPlugin can intercept --help before these errors are surfaced.

3. Pre-Compile (crust build)

crust build runs the same definition checks above across the full command tree — including plugin-injected flags and subcommands — before compiling. This catches issues like plugin-introduced alias collisions before shipping a binary. See the build guide for details.

On this page