AnyAgent

Errors

Every AnyAgentError code: when it fires, what it carries, and how to turn it into a message your end user can act on.

Everything AnyAgent throws is one class, AnyAgentError, with five code values and no subclass tree. A RunResult means the run succeeded, so a try/catch around the run is the whole story: catch AnyAgentError and branch on err.code, with nothing else to match against. Find the code you hit in its section below; the full example handles every one.

UnsupportedCapability

You asked for something this agent’s CLI cannot do: a capability-gated option (model, systemPrompt, resume, mcp, cwd, schema, effort, readOnly, attachments, forkSession) its capabilities do not declare, an effort value outside a closed vocabulary, or a discovery method (authStatus(), models()) it has no answer for. It fires before any process spawns, and the message names the agent and the gap, for example cline does not accept effort "ultra" (accepts: none, low, medium, high, xhigh).

Drop the option, or check first and degrade the feature when it is missing: agent.supports() for readOnly, effort, mcp, resume, attachments, and forkSession, agent.capabilities for the rest. Run an agent shows both guards, and the support matrix shows what every CLI declares.

InvalidOptions

The options contradict each other on any agent: a dependent option was passed without its prerequisite, or a session-owned option was passed by hand. Like UnsupportedCapability it fires before any process spawns, but this call could never be valid on any CLI, so correct the options at the call site rather than degrading per agent. The cases:

  • forkSession on a run with no resume, or fork at agent.session({ fork: true }) without resume: only an existing conversation can branch.
  • resume or forkSession passed to session.run(): the session owns continuity, so thread it through agent.session({ resume, fork }) instead.
  • Iterating one Run twice: a Run serves a single event consumer, so share the handle rather than opening a second for await.

Invocation

The CLI itself went wrong: it failed to spawn, exited nonzero, or reported an agent-level error in its own output stream. Real causes: the binary is missing (create never checks installation), the login expired, or the installed CLI does not know a flag you passed through extraArgs. argv and stderr are attached, so surface them to diagnose the failure. In a shipped tool, tell the end user to check that the agent is installed and signed in.

Parse

The CLI ran, but its output did not match the shape the adapter expects, or the stream ended without a terminal done event. This points to a change in the CLI’s output format that the adapter does not know yet: update anyagent, and report the failure upstream with the offending payload on err.raw.

Parse also fires when you pass a schema and the reply still fails to parse or validate after the one automatic retry. The message carries the validation errors and the agent’s final reply text is on err.raw. That case is your prompt or schema to adjust, not the adapter to update.

Aborted

Your AbortSignal fired and the underlying process was terminated, whether from a user cancelling or an AbortSignal.timeout expiring. In a shipped tool that means the end user stopped the run or your own timeout fired, so treat it as a normal exit rather than a failure. Run an agent shows how to wire the signal to Ctrl+C.

Diagnostic fields

Beyond message and code, three fields carry what ran and what the CLI said:

NameTypeDescription
rawunknownthe underlying cause: a native error, the CLI’s own error payload, or the reply that failed to parse
argvstring[]the exact command line that ran; attached on Invocation errors only
stderrstringwhat the CLI wrote to stderr; attached on Invocation errors only

Log all three inside the catch when a run fails unexpectedly, so a bug report is actionable:

console.error(err.message);
console.error("command:", err.argv?.join(" "));
console.error("stderr:", err.stderr);

Turn each code into a message for the end user

This wraps the acme init run and gives every code a response a stranger could act on:

import { create } from "anyagent";
import { AnyAgentError } from "anyagent/errors";
import { claudeCode } from "anyagent/claude-code";

const controller = new AbortController();
process.on("SIGINT", () => controller.abort());
const agent = create(claudeCode());

try {
  const result = await agent.run("Add the Acme SDK to this project.", {
    signal: controller.signal,
  });
  console.log(result.text);
} catch (err) {
  if (!(err instanceof AnyAgentError)) {
    throw err;
  }
  switch (err.code) {
    case "Aborted":
      console.log("Cancelled.");
      process.exit(0);
    case "UnsupportedCapability":
      console.error(
        `${agent.adapter.meta.name} cannot do that: ${err.message}`
      );
      break;
    case "InvalidOptions":
      console.error(`acme init passed contradictory options: ${err.message}`);
      break;
    case "Invocation":
      console.error(
        "The agent failed to run. Check that it is installed and signed in."
      );
      console.error(err.stderr);
      break;
    case "Parse":
      console.error(
        "Unexpected output from the agent. Please report this to Acme."
      );
      console.error(err.message);
      break;
    default:
      throw err;
  }
  process.exit(1);
}

Next steps

On this page