Throw Errors as Agent Trajectory Corrections

James Phoenix
James Phoenix

When AI agents drive development, they mutate state. They create files, run scripts, generate configs. Sometimes they skip a step or do something in the wrong order. Traditional error messages describe what went wrong for a human. But if the primary consumer of that error is an agent, the message should tell it how to recover.

The pattern: make throw new Error() messages into prompt instructions that redirect the agent’s trajectory.


The Problem: Agents Break Invariants Silently

Consider a monorepo where git worktrees must be created through a specific setup script. The script allocates deterministic ports, creates database schemas, and generates helper scripts. An agent that skips this script and creates a worktree manually will produce something that looks correct but violates critical invariants: no schema, no port allocation, no migration scripts.

The agent doesn’t know it went wrong. Tests pass locally because they run against the main database. The invariant violation surfaces later as port collisions, schema conflicts, or silent data corruption across parallel agents.

This is the core issue: agents don’t have institutional memory about how things should be done. They infer from context, and when context is incomplete, they take plausible shortcuts.


The Solution: Error Messages That Are Prompts

Instead of:

throw new Error('Worktree .env file is missing required keys')

Write:

Leanpub Book

Read The Meta-Engineer

A practical book on building autonomous AI systems with Claude Code, context engineering, verification loops, and production harnesses.

Continuously updated
Claude Code + agentic systems
View Book
throw new Error(
  [
    `Worktree .env is missing required keys: ${missingKeys.join(', ')}`,
    '',
    'This worktree was not initialized with the setup script, or its .env was',
    'manually edited and is now incomplete. The setup script generates all',
    'required values (deterministic ports, isolated DB schema, task queue).',
    '',
    'To fix, re-run the setup script:',
    `  bash scripts/worktree/setup.sh ${projectRoot}`,
    '',
    'Skill reference: .codex/skills/worktree/SKILL.md',
    'Do NOT manually populate these values. The setup script derives',
    'deterministic ports from the worktree name to prevent collisions',
    'across parallel agents.'
  ].join('\n')
)

The first message tells a human what broke. The second tells an agent exactly how to fix it, which script to run, and where to find the full workflow documentation. Critically, it also tells the agent what NOT to do: don’t manually populate the values. Without that negative instruction, an agent’s most likely recovery is to guess at the values and write them into .env itself, which is exactly the wrong move.


What We Built: Five Trajectory Corrections in Vitest Global Setup

We implemented this pattern in a production monorepo’s vitest-global-setup.ts. This file runs before every integration test suite, making it the earliest possible interception point. If an agent has set up a worktree incorrectly, it finds out before any test code executes, not after 30 minutes of cascading failures.

The worktree setup script (scripts/worktree/setup.sh) does five things: validates the worktree name, creates an isolated PostgreSQL schema, derives deterministic ports via hash-based allocation, generates a .env with all values, and creates helper scripts (run-migrations.sh, reset-worktree-schema.sh). Each of those can go wrong independently when an agent skips the script. So we added five corresponding validations, each with an agent-aware error message.

1. Missing .env file

The most basic check. If no .env exists, the agent never ran setup at all. The error distinguishes between worktree context and root repo context, giving different instructions for each.

2. Missing required env keys

The .env exists but is incomplete. This catches the case where an agent created the file by hand or copied .env.example and edited a few values. We check seven keys that the setup script generates: DATABASE_SCHEMA, API_PORT, WEB_PORT, MOBILE_PORT, WORKER_INSPECT_PORT, TEMPORAL_TASK_QUEUE, and WORKTREE_PORT_OFFSET. The error names every missing key so the agent understands the scope of the problem.

3. Missing helper scripts

The setup script generates run-migrations.sh and reset-worktree-schema.sh with the worktree’s database URL and schema name baked in. If these don’t exist, migrations will target the wrong schema. The error tells the agent not to create these manually because they contain interpolated values that must come from the setup script.

4. Default port collision guard

This is the most subtle check. An agent might run setup.sh but also might copy .env.example directly, which contains API_PORT=8080 and WEB_PORT=3000. These are the main worktree’s default dev ports. Inside a worktree, these will collide with the main dev server, causing EADDRINUSE errors that are difficult to diagnose. The error explains the hash-based port allocation system so the agent understands why its ports need to be different.

5. Integration lock timeout

If another worktree holds the integration lock, the agent is stuck. The original error said Timed out waiting for lock. The new error explains the single-writer constraint, gives three options (wait, force-release for stale locks, or fall back to pnpm type-check:quiet), and references the skill. This matters because an agent’s default response to a timeout is to retry, which will also time out.

Bonus: Lingering process cleanup

When a previous API server process can’t be stopped, the error now gives the exact kill -9 command and the specific pid file to remove, instead of just reporting the failure. It also warns about the pnpm dev rule from the worktree skill, because running dev and integration tests concurrently is the most common cause.


Why Vitest Global Setup Is the Right Place

Every agent treats test failure as a signal to change course. The vitest globalSetup hook runs before any test file loads, which means:

  • Failures are immediate, not buried in test output
  • The agent hasn’t committed to a path yet, so recovery is cheap
  • It runs every time, unlike CLAUDE.md which the agent might not read
  • The error message lands directly in the agent’s context window

Compare this to documenting the same invariants in a skill file. The skill file is passive context. The agent has to read it, understand it, and follow it. The globalSetup error is active enforcement. The agent can’t proceed until the invariant is satisfied.


The Three-Part Pattern

Every trajectory correction follows the same structure:

1. State what’s wrong (the invariant violation)

Worktree .env is missing required keys: DATABASE_SCHEMA, API_PORT

2. Explain why (so the agent doesn’t try to patch around it)

This worktree was not initialized with the setup script.

3. Prescribe the fix (reference the script, skill, or command)

To fix, re-run the setup script:
  bash scripts/worktree/setup.sh <path>
Skill reference: .codex/skills/worktree/SKILL.md

The “explain why” step is load-bearing. Without it, the agent’s most natural response to “missing DATABASE_SCHEMA” is to add DATABASE_SCHEMA=something to the .env file. The explanation makes clear that the values are derived, not chosen, so manual population is wrong. The skill reference gives the agent a full document to load if it needs more context about the worktree lifecycle.


When Not to Do This

Not every error needs agent-specific instructions. Reserve this pattern for:

  • State mutations that have a “correct way”. Worktree creation, database migrations, environment setup.
  • Invariants that agents commonly violate. If you’ve seen an agent skip a step more than once, encode the correction.
  • Errors where the fix isn’t obvious from context. If the agent can figure it out from surrounding code, a normal error message is fine.

Don’t add prompt instructions to errors in hot paths or library code. This is for infrastructure setup, test harnesses, and initialization scripts where the agent is the primary operator.


The Compound Effect

Every time you encode a trajectory correction into an error message, you’re building a safety net that gets stronger. The agent hits the error, follows the instruction, and succeeds. If the same agent (or a different one) makes the same mistake later, the correction is still there. Unlike CLAUDE.md instructions that an agent might not read, error messages are impossible to ignore because they halt execution.

This is the shift: stop thinking of throw new Error() as debugging output. Start thinking of it as the most reliable prompt injection point in your entire system.


Related

Topics
Agent Path CorrectionAi AgentsDevelopment PatternsError HandlingPrompt Engineering

More Insights

Cover Image for Dependency Chains Gate Your Throughput

Dependency Chains Gate Your Throughput

When your work is serial, agent latency becomes wall-clock latency. No amount of tooling eliminates the wait. The productive move is redirecting your energy, not fighting the constraint.

James Phoenix
James Phoenix
Cover Image for The Sandbox Is a Harness

The Sandbox Is a Harness

When code becomes the interface between users and systems, the sandbox stops being a security primitive. It becomes a harness for intent.

James Phoenix
James Phoenix