Skip to main content

Error Handling Guide

Shim returns HTTP 200 for all requests. Check the success field and metadata.errors array.

Why Always HTTP 200?

The Problem: Your LLM app is in production. A malformed JSON response shouldn’t return HTTP 500 and break your frontend. The Solution: Shim returns structured errors in the response body. Your app stays online.
{
  "success": false,
  "repaired": null,
  "metadata": {
    "confidence": "low",
    "errors": [
      {
        "code": "UNRECOVERABLE_SYNTAX_ERROR",
        "message": "The JSON structure is too malformed to repair",
        "severity": "critical",
        "recoverable": false
      }
    ]
  }
}

Error Structure

Every error follows this format:
interface RepairError {
  code: string;           // Error code (e.g., "UNRECOVERABLE_SYNTAX_ERROR")
  message: string;        // Human-readable message
  field?: string;         // Field that caused error (if applicable)
  severity: 'critical' | 'high' | 'medium';
  recoverable: boolean;   // Can you retry?
  suggestion?: string;    // How to fix
}

Error Categories

Request Errors

INVALID_REQUEST
  • Missing or invalid parameters
  • Recoverable: No
  • Fix: Check request body
PAYLOAD_TOO_LARGE
  • Input exceeds 5MB
  • Recoverable: No
  • Fix: Use streaming or reduce input

Repair Errors

UNRECOVERABLE_SYNTAX_ERROR
  • JSON structure too malformed to repair after syntax repair
  • Recoverable: No
  • Fix: Check LLM output format
SCHEMA_VALIDATION_FAILED
  • Repaired JSON doesn’t match schema
  • Recoverable: No
  • Fix: Review schema or LLM prompt

Session Errors

SESSION_NOT_FOUND
  • Session expired (60s TTL)
  • Recoverable: Yes
  • Fix: Create new session
BUFFER_SIZE_EXCEEDED
  • Buffer exceeded 5MB (hallucination loop)
  • Recoverable: No
  • Fix: Add max_tokens limit to LLM

Service Errors

SERVICE_UNAVAILABLE
  • Server at capacity
  • Recoverable: Yes
  • Fix: Retry with exponential backoff
RATE_LIMIT_EXCEEDED
  • Exceeded tier limit
  • Recoverable: Yes (after wait)
  • Fix: Upgrade tier or wait

Checking for Errors

Basic Check

const result = await shim.repair({ raw_output: input });

if (!result.success) {
  console.error('Repair failed:', result.metadata.errors);
  return null;
}

return result.repaired;

Check Recoverability

const result = await shim.repair({ raw_output: input });

if (!result.success) {
  const errors = result.metadata.errors;
  const canRetry = errors.every(e => e.recoverable);

  if (canRetry) {
    // Retry with backoff
    await retryRepair(input);
  } else {
    // Log and alert
    logger.error('Unrecoverable repair error', { errors });
    alertOps('Shim repair failed', errors);
  }
}

Check Specific Error Codes

const result = await shim.repair({ raw_output: input });

if (!result.success) {
  const error = result.metadata.errors[0];

  switch (error.code) {
    case 'UNRECOVERABLE_SYNTAX_ERROR':
      // LLM output is garbage
      logger.warn('LLM returned invalid output');
      break;

    case 'RATE_LIMIT_EXCEEDED':
      // Hit tier limit
      await new Promise(r => setTimeout(r, 60000)); // Wait 1min
      return retryRepair(input);

    case 'SESSION_NOT_FOUND':
      // Session expired, restart
      return restartSession();

    default:
      logger.error('Unexpected error', { error });
  }
}

Retry Strategies

Exponential Backoff

async function repairWithBackoff(
  input: string,
  maxRetries = 3
): Promise<any> {
  for (let i = 0; i < maxRetries; i++) {
    const result = await shim.repair({ raw_output: input });

    if (result.success) {
      return result.repaired;
    }

    // Check if recoverable
    const canRetry = result.metadata.errors.every(e => e.recoverable);
    if (!canRetry) {
      throw new Error('Unrecoverable error');
    }

    // Wait before retry: 1s, 2s, 4s
    const delay = Math.pow(2, i) * 1000;
    await new Promise(r => setTimeout(r, delay));
  }

  throw new Error('Max retries exceeded');
}

Circuit Breaker

class ShimCircuitBreaker {
  private failures = 0;
  private lastFailure = 0;
  private readonly threshold = 5;
  private readonly resetTimeout = 60000; // 1 minute

  async repair(input: string) {
    // Check if circuit is open
    if (this.isOpen()) {
      throw new Error('Circuit breaker open');
    }

    try {
      const result = await shim.repair({ raw_output: input });

      if (result.success) {
        this.reset();
        return result.repaired;
      }

      this.recordFailure();
      throw new Error('Repair failed');
    } catch (error) {
      this.recordFailure();
      throw error;
    }
  }

  private isOpen(): boolean {
    if (this.failures >= this.threshold) {
      const elapsed = Date.now() - this.lastFailure;
      if (elapsed < this.resetTimeout) {
        return true;
      }
      this.reset();
    }
    return false;
  }

  private recordFailure() {
    this.failures++;
    this.lastFailure = Date.now();
  }

  private reset() {
    this.failures = 0;
    this.lastFailure = 0;
  }
}

Warnings vs Errors

Errors (Critical)

Repair failed. success: false.
{
  "success": false,
  "repaired": null,
  "metadata": {
    "errors": [
      {
        "code": "UNRECOVERABLE_SYNTAX_ERROR",
        "severity": "critical"
      }
    ]
  }
}

Warnings (Non-Critical)

Repair succeeded, but review recommended. success: true.
{
  "success": true,
  "repaired": { "name": "John" },
  "metadata": {
    "confidence": "medium",
    "warnings": [
      {
        "code": "AMBIGUOUS_REPAIR",
        "message": "Multiple repair interpretations possible",
        "severity": "medium"
      }
    ]
  }
}
Handle warnings:
const result = await shim.repair({ raw_output: input });

if (result.success) {
  // Check warnings
  if (result.metadata.warnings.length > 0) {
    logger.warn('Repair succeeded with warnings', {
      warnings: result.metadata.warnings
    });
  }

  return result.repaired;
}

Confidence Levels

Confidence indicates repair safety:
LevelMeaningAction
highStructural fixes only, or perfect inputUse immediately
mediumSchema repairs or warningsReview in logs
lowAmbiguous repairsManual review
const result = await shim.repair({ raw_output: input });

if (result.success) {
  switch (result.metadata.confidence) {
    case 'high':
      // Safe to use
      return result.repaired;

    case 'medium':
      // Log for review
      logger.info('Medium confidence repair', {
        repairs: result.metadata.repairs_applied
      });
      return result.repaired;

    case 'low':
      // Alert for manual review
      logger.warn('Low confidence repair', {
        repairs: result.metadata.repairs_applied
      });
      alertOps('Low confidence Shim repair');
      return result.repaired;

    // 'high' also covers perfect input (was_repaired: false)
  }
}
See Confidence Levels for details.

Logging Best Practices

Log Errors with Context

const result = await shim.repair({ raw_output: input });

if (!result.success) {
  logger.error('Shim repair failed', {
    errors: result.metadata.errors,
    input_length: input.length,
    request_id: response.headers.get('X-Request-ID')
  });
}

Track Repair Metrics

const result = await shim.repair({ raw_output: input });

// Track success rate
metrics.increment('shim.repairs.total');
if (result.success) {
  metrics.increment('shim.repairs.success');
} else {
  metrics.increment('shim.repairs.failed');
}

// Track confidence distribution
metrics.increment(`shim.confidence.${result.metadata.confidence}`);

// Track repair types
result.metadata.repairs_applied.forEach(repair => {
  metrics.increment(`shim.repair_type.${repair.type}`);
});

Alert on Failure Spikes

const failures = await metrics.query('shim.repairs.failed', '5m');
const total = await metrics.query('shim.repairs.total', '5m');
const failureRate = failures / total;

if (failureRate > 0.1) {
  alertOps('High Shim failure rate', {
    rate: failureRate,
    failures,
    total
  });
}

Fallback Strategies

Fallback to Raw Output

async function repairOrFallback(input: string) {
  const result = await shim.repair({ raw_output: input });

  if (result.success) {
    return result.repaired;
  }

  // Fallback: try parsing raw input
  try {
    return JSON.parse(input);
  } catch {
    // Both failed
    logger.error('Shim and JSON.parse failed');
    return null;
  }
}

Fallback to Default Value

async function repairOrDefault(input: string, defaultValue: any) {
  const result = await shim.repair({ raw_output: input });

  if (result.success) {
    return result.repaired;
  }

  logger.warn('Using default value due to repair failure');
  return defaultValue;
}

Retry with Looser Schema

async function repairWithFallback(input: string, schema: JSONSchema) {
  // Try strict mode first
  let result = await shim.repair({
    raw_output: input,
    schema,
    mode: 'strict'
  });

  if (result.success) {
    return result.repaired;
  }

  // Fallback to lenient mode
  logger.warn('Retrying in lenient mode');
  result = await shim.repair({
    raw_output: input,
    schema,
    mode: 'lenient'
  });

  return result.success ? result.repaired : null;
}

Production Checklist

  • Check success field on every response
  • Log errors with request ID
  • Implement retry logic for recoverable errors
  • Track failure rate metrics
  • Set up alerts for high failure rates
  • Have fallback strategy for critical paths
  • Review low-confidence repairs in logs
  • Monitor repair type distribution

Next Steps

Error Codes

Complete error reference

Confidence Levels

Understand confidence scoring

Rate Limits

Handle RATE_LIMIT_EXCEEDED

Troubleshooting

Common issues and fixes