Skip to content

ComfyUI Error Patterns ​

How to add and manage ComfyUI workflow error patterns in the TypeScript-only error handling system.

šŸŽÆ The ONE PLACE for ComfyUI Errors ​

When you see an unclassified ComfyUI error in production, there is ONE PLACE to go:

šŸ“ packages/core/src/errors/comfyui-error-enhancer.ts

Why TypeScript-Only? ​

Per ERROR_HANDLING_MODERNIZATION.md ADR:

  1. ComfyUI is an upstream fork - The less we modify it, the easier it is to merge updates
  2. ComfyUI builds are slow - 64 custom nodes = long rebuild times
  3. TypeScript is source of truth - One place to maintain error logic
  4. No Python changes needed - Add patterns without ComfyUI rebuild

Step-by-Step: Adding a New Pattern ​

Example Production Error ​

You see this in the event stream:

KeyError: 'url'
File "/workspace/ComfyUI/custom_nodes/emprops_comfy_nodes/nodes/emprops_image_loader.py", line 66, in load_image
    print(f"[EmProps] Downloading from URL: {kwargs['url']}", flush=True)
                                             ~~~~~~^^^^^^^

Step 1: Identify the Pattern ​

The key pattern is: KeyError: 'url'

Additional context:

  • Exception type: KeyError
  • Missing field: url
  • Node: emprops_image_loader

Step 2: Open THE ONE PLACE ​

bash
code packages/core/src/errors/comfyui-error-enhancer.ts

Step 3: Add Pattern Matcher ​

Find the // PATTERN MATCHERS section and add your pattern:

typescript
// KeyError: Missing required parameter/field (generic KeyError pattern)
// Example: "KeyError: 'url'" from emprops_image_loader.py:66
// Example: "KeyError: 'source_type'" from custom nodes
if (exceptionType.toLowerCase() === 'keyerror' || message.toLowerCase().includes('keyerror')) {
  const fieldMatch = message.match(/KeyError:\s*['"]([^'"]+)['"]/i);
  const fieldName = fieldMatch ? fieldMatch[1] : 'unknown';

  return new ConnectorError(
    FailureType.VALIDATION_ERROR,
    FailureReason.MISSING_REQUIRED_FIELD,
    `Missing required field '${fieldName}' in node ${error.node_type || 'unknown'}`,
    false, // Not retryable - user must fix workflow
    {
      serviceType: 'comfyui',
      node: error.node_id ? { id: error.node_id, type: error.node_type } : undefined,
      component: context?.component,
      workflow: context?.workflow,
      jobId: context?.jobId,
      suggestion: `Provide the required '${fieldName}' parameter in your workflow configuration`,
      missingField: fieldName,
      rawError: error
    }
  );
}

Step 4: Build and Test ​

bash
# Build core package
pnpm --filter=@emp/core build

# Create test file
cat > /tmp/test-keyerror.mjs << 'EOF'
import { ComfyUIErrorEnhancer } from '/Users/the_dusky/code/emerge/emerge-turbo/packages/core/dist/errors/comfyui-error-enhancer.js';

const error = {
  exception_message: "KeyError: 'url'",
  exception_type: 'KeyError',
  node_id: '66',
  node_type: 'emprops_image_loader'
};

const result = ComfyUIErrorEnhancer.enhance(error, {
  component: 'Image Loader',
  workflow: 'test-workflow'
});

console.log('āœ… Type:', result.failureType);
console.log('āœ… Reason:', result.failureReason);
console.log('āœ… Message:', result.message);
console.log('āœ… Suggestion:', result.context.suggestion);
console.log('āœ… Missing Field:', result.context.missingField);

if (result.failureReason === 'missing_required_field' && result.context.missingField === 'url') {
  console.log('\nāœ… PASS - KeyError correctly classified!');
} else {
  console.error('\nāŒ FAIL - Incorrect classification');
  process.exit(1);
}
EOF

# Run test
node /tmp/test-keyerror.mjs

Step 5: Done! āœ… ​

No Python changes, no ComfyUI rebuild required.

Pattern Order Matters ​

More specific patterns should come BEFORE generic patterns:

typescript
// āœ… CORRECT ORDER:

// 1. Missing custom node (specific KeyError case)
if (message.match(/node .+ does not exist/i)) { ... }

// 2. Generic KeyError (generic pattern)
if (exceptionType === 'keyerror') { ... }
typescript
// āŒ WRONG ORDER:

// 1. Generic KeyError (catches everything)
if (exceptionType === 'keyerror') { ... }

// 2. Missing custom node (never reached!)
if (message.match(/node .+ does not exist/i)) { ... }

Common Error Patterns ​

GPU Out of Memory ​

typescript
// GPU Out of Memory
// Example: "CUDA out of memory. Tried to allocate 2.00 GiB"
if (message.includes('CUDA out of memory') ||
    message.includes('GPU memory') ||
    message.includes('CUDA error: out of memory')) {
  return new ConnectorError(
    FailureType.RESOURCE_LIMIT,
    FailureReason.GPU_MEMORY_FULL,
    'GPU ran out of memory during workflow execution',
    true, // Retryable with reduced settings
    {
      serviceType: 'comfyui',
      node: error.node_id ? { id: error.node_id, type: error.node_type } : undefined,
      suggestion: 'Try reducing image size, batch size, or use a lower resolution. Current settings exceeded GPU capacity.',
      rawError: error
    }
  );
}

CPU/System RAM Out of Memory ​

typescript
// CPU/System RAM Out of Memory
if ((message.includes('out of memory') && !message.includes('CUDA')) ||
    exceptionType.toLowerCase() === 'memoryerror' ||
    message.includes('cannot allocate memory')) {
  return new ConnectorError(
    FailureType.RESOURCE_LIMIT,
    FailureReason.OUT_OF_MEMORY,
    'System RAM exhausted during workflow execution',
    true,
    {
      serviceType: 'comfyui',
      suggestion: 'Try reducing batch size, closing other applications, or use less memory-intensive settings',
      rawError: error
    }
  );
}

Missing Model ​

typescript
// Model not found
// Example: "Checkpoint 'sd_xl_base_1.0.safetensors' not found"
if (message.match(/checkpoint|model.+not found|does not exist/i)) {
  const modelMatch = message.match(/'([^']+)'/);
  const modelName = modelMatch ? modelMatch[1] : 'unknown';
  return new ConnectorError(
    FailureType.VALIDATION_ERROR,
    FailureReason.MODEL_NOT_FOUND,
    `Model '${modelName}' not found in ComfyUI`,
    false,
    {
      serviceType: 'comfyui',
      suggestion: `Install the model '${modelName}' or select a different model from the available list.`,
      missingModel: modelName,
      rawError: error
    }
  );
}

Missing Custom Node ​

typescript
// Missing custom node (specific KeyError case)
// Example: "Node 'FooNode' does not exist"
if (message.match(/node .+ does not exist/i)) {
  const nodeMatch = message.match(/node '?(\S+?)'? does not exist/i);
  const nodeName = nodeMatch ? nodeMatch[1] : 'unknown';
  return new ConnectorError(
    FailureType.VALIDATION_ERROR,
    FailureReason.UNSUPPORTED_OPERATION,
    `Custom node '${nodeName}' is not installed`,
    false,
    {
      serviceType: 'comfyui',
      suggestion: `Install the '${nodeName}' custom node from ComfyUI Manager before running this workflow.`,
      missingNode: nodeName,
      rawError: error
    }
  );
}

FailureType and FailureReason ​

FailureType (High-Level) ​

typescript
enum FailureType {
  VALIDATION_ERROR      // Input validation, missing fields
  RESOURCE_LIMIT        // OOM, disk space, GPU limits
  MODEL_ERROR           // Model not found, loading failed
  CUSTOM_NODE_ERROR     // Node missing or execution failed
  SYSTEM_ERROR          // Unknown/unexpected errors
  TIMEOUT               // Operation took too long
}

FailureReason (Specific) ​

typescript
enum FailureReason {
  // Validation
  MISSING_REQUIRED_FIELD
  INVALID_PARAMETER
  TYPE_MISMATCH

  // Resources
  GPU_MEMORY_FULL
  OUT_OF_MEMORY
  DISK_SPACE_FULL

  // Models
  MODEL_NOT_FOUND
  MODEL_LOAD_FAILED

  // Custom Nodes
  UNSUPPORTED_OPERATION  // Node not installed
  MISSING_CUSTOM_NODE

  // System
  UNKNOWN_ERROR
  TIMEOUT
}

Variable Interpolation ​

You can extract values from errors and include them in messages:

typescript
// Extract field name from KeyError
const fieldMatch = message.match(/KeyError:\s*['"]([^'"]+)['"]/i);
const fieldName = fieldMatch ? fieldMatch[1] : 'unknown';

// Use in message
`Missing required field '${fieldName}' in node ${error.node_type}`

// Use in context
{
  suggestion: `Provide the required '${fieldName}' parameter`,
  missingField: fieldName  // Available in context.missingField
}

What NOT to Do ​

āŒ Don't Modify Python (ComfyUI) ​

python
# āŒ BAD - Modifying upstream fork
# packages/comfyui/execution.py
if "keyerror" in exception_type.lower():
    return StructuredError(code=ErrorCode.VALIDATION_REQUIRED_INPUT_MISSING)

āŒ Don't Add ErrorCode Enums in Python ​

python
# āŒ BAD - Duplicating logic in Python
class ErrorCode(str, Enum):
    VALIDATION_REQUIRED_INPUT_MISSING = "VALIDATION_REQUIRED_INPUT_MISSING"

āœ… Do This Instead ​

typescript
// āœ… GOOD - TypeScript-only (THE ONE PLACE)
// packages/core/src/errors/comfyui-error-enhancer.ts
if (exceptionType === 'keyerror') {
  return new ConnectorError(...)
}

Debugging ​

Check Classification ​

bash
# 1. Trigger error in ComfyUI workflow
# 2. Check worker logs
docker logs <worker-container> -f | grep "Critical error"

# 3. Check monitor UI
open http://localhost:3001
# Click on failed job → See error message and suggestion

Verify Pattern Matching ​

typescript
// Add debug logging to enhancer
static enhance(error: ComfyUIError, context?: ComfyUIErrorContext): ConnectorError {
  const message = error.exception_message || error.message || '';
  console.log('[DEBUG] Classifying error:', {
    message,
    exceptionType: error.exception_type,
    nodeType: error.node_type
  });

  // ... pattern matching ...
}

Testing Strategy ​

Unit Test ​

typescript
// packages/core/src/__tests__/comfyui-error-enhancer.test.ts
import { describe, it, expect } from 'vitest';
import { ComfyUIErrorEnhancer } from '../errors/comfyui-error-enhancer';

describe('ComfyUIErrorEnhancer', () => {
  it('should classify KeyError correctly', () => {
    const error = {
      exception_message: "KeyError: 'url'",
      exception_type: 'KeyError',
      node_id: '66',
      node_type: 'emprops_image_loader'
    };

    const result = ComfyUIErrorEnhancer.enhance(error);

    expect(result.failureReason).toBe('missing_required_field');
    expect(result.context.missingField).toBe('url');
    expect(result.message).toContain('url');
  });
});

Integration Test ​

bash
# 1. Start local development environment
pnpm dev:local-redis

# 2. Start basic machine
pnpm machines:basic:local:up:build

# 3. Submit job with missing field (trigger KeyError)
curl -X POST http://localhost:3100/jobs \
  -H "Content-Type: application/json" \
  -d '{
    "service": "comfyui",
    "payload": {
      "workflow": {
        "nodes": {
          "1": {
            "class_type": "emprops_image_loader",
            "inputs": {
              // Missing "url" field - will trigger KeyError
            }
          }
        }
      }
    }
  }'

# 4. Check monitor UI for error message
open http://localhost:3001

Best Practices ​

DO ​

āœ… Add code comments with examples āœ… Extract variable values (field names, model names, etc.) āœ… Provide actionable suggestions āœ… Test patterns before deploying āœ… Use specific FailureReason values

DON'T ​

āŒ Don't add Python error classification āŒ Don't use generic error messages āŒ Don't skip testing āŒ Don't put specific patterns after generic ones āŒ Don't create new FailureType/FailureReason without good reason

Quick Reference ​

typescript
// Template for new ComfyUI error pattern
// Location: packages/core/src/errors/comfyui-error-enhancer.ts

// {Description} - {What triggers it}
// Example: "{Actual error message from logs}"
if (exceptionType.toLowerCase() === '{type}' || message.includes('{pattern}')) {
  // Extract variables if needed
  const someValue = message.match(/{regex}/)?.[1] || 'unknown';

  return new ConnectorError(
    FailureType.{TYPE},
    FailureReason.{REASON},
    '{User-friendly message with ${someValue}}',
    {retryable}, // true or false
    {
      serviceType: 'comfyui',
      node: error.node_id ? { id: error.node_id, type: error.node_type } : undefined,
      component: context?.component,
      workflow: context?.workflow,
      jobId: context?.jobId,
      suggestion: '{How to fix it}',
      {customField}: someValue,
      rawError: error
    }
  );
}

Released under the MIT License.