Skip to content

Guides

Run Claude Agent SDK agents in a Promigence sandbox

Keep the Claude Agent SDK loop in your process and run Claude Code itself inside a Promigence sandbox, with your Anthropic key stored as a secret.

Also as markdown: /docs/guides/claude-agent-sdk.md

The Claude Agent SDK starts Claude Code as a child process and talks to it over stdin and stdout. Its spawnClaudeCodeProcess option lets you replace that spawn. The promigence-claude-agent-sdk package gives you a function for that option, so Claude Code runs inside a Promigence sandbox while your program keeps the loop, reads the messages and decides what happens next. Anything the agent edits, installs or deletes stays in the sandbox.

What you need#

  • A Promigence API key, exported as PROMIGENCE_API_KEY.
  • Node.js 20 or newer.
  • @anthropic-ai/claude-agent-sdk 0.3.276 or newer. The adapter was written and tested against 0.3.276.
  • An Anthropic API key, stored once as a Promigence secret (below).

Install#

Terminal
npm install promigence-claude-agent-sdk @anthropic-ai/claude-agent-sdk promigence
export PROMIGENCE_API_KEY=prom_api_...
promigence secrets set ANTHROPIC_API_KEY --from-env ANTHROPIC_API_KEY
Language: bash

promigence secrets set reads the value from your ANTHROPIC_API_KEY variable and stores it write-only. From then on your code only ever names the secret. The value goes into the agent's environment inside the sandbox; it is never put on a command line or written to a file there, and the package refuses a credential passed as a plain environment value.

A minimal example#

The sandbox needs Claude Code installed. The built-in base snapshot has Node.js, so one npm install inside the sandbox is enough. You also have to tell the SDK where the executable lives inside the sandbox, because the path it would find on your own machine does not exist there.

TypeScript
import { query } from "@anthropic-ai/claude-agent-sdk";
import { PromigenceClient, Sandbox } from "promigence";
import { spawnInPromigence } from "promigence-claude-agent-sdk";

const client = new PromigenceClient();
const sandbox = await Sandbox.create({ client, timeoutMs: 15 * 60_000, spendCap: 5 });
await sandbox.commands.run("command -v claude || npm install -g @anthropic-ai/claude-code");

try {
  for await (const message of query({
    prompt: "Fix the failing test in src/parser.ts",
    options: {
      pathToClaudeCodeExecutable: "/usr/local/bin/claude",
      spawnClaudeCodeProcess: spawnInPromigence({
        client,
        sandboxId: sandbox.sandboxId,
        cwd: "/workspace",
        secrets: ["ANTHROPIC_API_KEY"],
        onStderr: (text) => process.stderr.write(text),
      }),
    },
  })) {
    if (message.type === "result") console.error(`[${message.subtype}]`);
  }
} finally {
  await sandbox.kill();
}
Language: ts

If you would rather not install Claude Code yourself, promigence agent start claude prepares a sandbox with Claude Code in it, binds your stored ANTHROPIC_API_KEY, and prints the sandbox id to pass as sandboxId.

spawnInPromigence takes a few more options. executablePath overrides where Claude Code is inside the sandbox. env adds plain settings. envForward controls which of the SDK's own environment travels: by default only CLAUDE_* and ANTHROPIC_* settings, and never anything shaped like a credential. Put a token you do want the agent to have in secrets after storing it with promigence secrets set.

One sandbox per query#

You create the sandbox and you end it. The package attaches one Claude Code process to the sandbox you name; it does not create, copy or end sandboxes on its own. The usual shape is one sandbox per task: create it, run the query, kill it in a finally block as above.

Running two agents in the same sandbox at once needs a different envDir and stderrPath for each, since both default to fixed paths.

Setting a spend cap#

Pass spendCap (US dollars) to Sandbox.create, or set PROMIGENCE_SPEND_CAP in the environment. Without either, your organization's default cap applies. The cap is the most that sandbox may spend; Promigence stops it rather than let it go over.

Cleaning up#

await sandbox.kill() ends the sandbox and its billing. Put it in finally so a thrown error or an interrupted loop does not leave one running. timeoutMs is a second guard: the sandbox ends on its own when that time is up. promigence sandbox list shows anything still running, and promigence sandbox kill <id> ends one by hand.

What is not supported yet#

  • Closing the agent's stdin does not end it. A query finishes when the SDK stops the agent after its own grace period.
  • There is no live stderr stream. onStderr receives the tail once the agent exits.
  • The SDK's local debug file and session store do not carry across to a remote agent.
  • If pathToClaudeCodeExecutable ends in .js or .mjs, set executable: "node" in the SDK options and leave executablePath unset.
  • Multi-line environment values lose trailing newlines.
  • TypeScript only. The Python Agent SDK has no equivalent spawn hook, so there is no Python version.

Next#

Read the agent briefing for what a sandbox gives an agent, or start with the quickstart.

Get started

Leave your work email and we will get you started.

See our Privacy Policy for how we use your email.