Skip to main content

Error Handling Guide

Strategies for handling errors in Smithers workflows.

Basic Error Handling

Use the onError callback:
<Claude
  onFinished={(result) => console.log("Success:", result.output)}
  onError={(error) => console.error("Failed:", error.message)}
>
  Perform the task.
</Claude>

Retry with State

Implement retry logic with signals:
import { useQueryValue } from "smithers-orchestrator/reactive-sqlite";

function RetryWorkflow() {
  const { db, reactiveDb } = useSmithers();

  const attempts = useQueryValue<number>(
    reactiveDb,
    "SELECT CAST(value AS INTEGER) FROM state WHERE key = 'attempts'"
  ) ?? 0;

  const lastError = useQueryValue<string>(
    reactiveDb,
    "SELECT value FROM state WHERE key = 'lastError'"
  );

  return (
    <SmithersProvider db={db} executionId={executionId} maxIterations={5}>
      <If condition={attempts < 3}>
        <Claude
          onFinished={() => db.state.set("lastError", null)}
          onError={(err) => {
            db.state.set("lastError", err.message);
            db.state.set("attempts", attempts + 1);
          }}
        >
          <If condition={lastError !== null}>
            <>Previous attempt failed: {lastError}</>
          </If>
          Attempt the task (attempt {attempts + 1}/3).
        </Claude>
      </If>

      <If condition={attempts >= 3}>
        <Stop reason={`Failed after 3 attempts: ${lastError}`} />
      </If>
    </SmithersProvider>
  );
}

Orchestration-Level Errors

Handle errors at the orchestration level:
<SmithersProvider
  db={db}
  executionId={executionId}
  globalTimeout={3600000}
  onError={async (error) => {
    // Log to database
    await db.vcs.addReport({
      type: "error",
      severity: "critical",
      title: "Workflow Failed",
      content: error.message,
    });

    // Send notification
    await sendAlert(`Workflow failed: ${error.message}`);
  }}
>
  <WorkflowContent />
</SmithersProvider>

Stop Conditions

Use stop conditions to catch issues early:
<SmithersProvider
  db={db}
  executionId={executionId}
  stopConditions={[
    { type: "total_tokens", value: 50000 },
    { type: "report_severity", value: "critical" },
    {
      type: "custom",
      fn: (result) => result.output.includes("FATAL"),
      message: "Fatal error detected",
    },
  ]}
  onStopRequested={() => {
    console.log("Workflow stopped by condition");
  }}
>

Database Logging

Log errors to the database for debugging:
<Claude
  onError={async (error) => {
    // Log the failure
    await db.vcs.addReport({
      type: "error",
      severity: "warning",
      title: "Agent Failed",
      content: error.message,
      metadata: {
        phase: phase,
        attempts: attempts,
        timestamp: new Date().toISOString(),
      },
    });

    // Store learning for future
    await db.memories.addLearning(
      `error-${Date.now()}`,
      `Encountered error: ${error.message}`,
      "error handler"
    );
  }}
>

Graceful Degradation

Fall back to simpler approaches:
import { useQueryValue } from "smithers-orchestrator/reactive-sqlite";

function ResilientWorkflow() {
  const { db, reactiveDb } = useSmithers();

  const approach = useQueryValue<string>(
    reactiveDb,
    "SELECT value FROM state WHERE key = 'approach'"
  ) ?? "full";

  const setApproach = (newApproach: string) => {
    db.state.set("approach", newApproach);
  };

  return (
    <SmithersProvider db={db} executionId={executionId} maxIterations={5}>
      <If condition={approach === "full"}>
        <Claude
          model="opus"
          maxTurns={20}
          onFinished={() => setApproach("done")}
          onError={() => setApproach("simple")}
        >
          Comprehensive analysis with full toolset.
        </Claude>
      </If>

      <If condition={approach === "simple"}>
        <Claude
          model="sonnet"
          maxTurns={5}
          allowedTools={["Read"]}
          onFinished={() => setApproach("done")}
          onError={() => setApproach("manual")}
        >
          Simplified analysis, read-only.
        </Claude>
      </If>

      <If condition={approach === "manual"}>
        <Human
          message="Automated analysis failed"
          onApprove={() => setApproach("done")}
        >
          Both automated approaches failed.
          Please review manually.
        </Human>
      </If>
    </SmithersProvider>
  );
}

Validation Errors

Handle schema validation failures:
<Claude
  schema={StrictSchema}
  maxRetries={2}
  onFinished={(result) => {
    if (!result.structured) {
      console.warn("No structured output");
      return;
    }
    // Use structured output
  }}
  onError={(error) => {
    if (error.message.includes("validation")) {
      console.error("Schema validation failed after retries");
      // Fall back to unstructured output
    }
  }}
>

Timeout Handling

Handle timeouts explicitly:
<Claude
  timeout={60000}  // 1 minute
  onError={(error) => {
    if (error.message.includes("timeout")) {
      console.log("Agent timed out, trying shorter task");
      setApproach("shorter");
    }
  }}
>

Complete Error Handling Pattern

import { useQueryValue } from "smithers-orchestrator/reactive-sqlite";

function RobustWorkflow() {
  const { db, reactiveDb } = useSmithers();

  const phase = useQueryValue<string>(
    reactiveDb,
    "SELECT value FROM state WHERE key = 'phase'"
  ) ?? "start";

  const attempts = useQueryValue<number>(
    reactiveDb,
    "SELECT CAST(value AS INTEGER) FROM state WHERE key = 'attempts'"
  ) ?? 0;

  const errorsJson = useQueryValue<string>(
    reactiveDb,
    "SELECT value FROM state WHERE key = 'errors'"
  );
  const errors: string[] = errorsJson ? JSON.parse(errorsJson) : [];

  return (
    <SmithersProvider
      db={db}
      executionId={executionId}
      globalTimeout={3600000}
      maxIterations={10}
      stopConditions={[
        { type: "total_tokens", value: 100000 },
      ]}
      onError={async (error) => {
        await db.vcs.addReport({
          type: "error",
          severity: "critical",
          title: "Workflow Error",
          content: error.message,
        });
      }}
      onMaxIterations={() => {
        console.error("Max iterations reached");
      }}
    >
      <If condition={phase === "start" && attempts < 3}>
        <Claude
          onFinished={() => db.state.set("phase", "done")}
          onError={(error) => {
            const newErrors = [...errors, error.message];
            db.state.set("errors", JSON.stringify(newErrors));
            db.state.set("attempts", attempts + 1);
            if (attempts >= 2) {
              db.state.set("phase", "fallback");
            }
          }}
        >
          Primary task.
          <If condition={errors.length > 0}>
            <>Previous errors: {errors.join(", ")}</>
          </If>
        </Claude>
      </If>

      <If condition={phase === "fallback"}>
        <Human
          message="Automated task failed"
          onApprove={() => db.state.set("phase", "done")}
          onReject={() => db.state.set("phase", "cancelled")}
        >
          Failed {attempts} times.
          Errors: {errors.join("; ")}
        </Human>
      </If>

      <If condition={phase === "cancelled"}>
        <Stop reason="Cancelled after failures" />
      </If>
    </SmithersProvider>
  );
}