Skip to content

Claude Code

Image updates

Ready-made agent images are updated every morning to include the current CLI version. To use a fixed image version, pin a template version.

Claude Code is an Anthropic agent that reads and changes files, runs commands, and connects external tools. AgentBox runs it inside a separate sandbox. The claude template already includes the CLI.

Connect

Install the AgentBox SDK and set AGENTBOX_API_KEY and ANTHROPIC_API_KEY in your application environment. The example passes the Anthropic key only to the agent command. --model sonnet selects a Claude Code model alias. You can instead specify a full model name available to your key.

To access the model, you can use the AgentBox HTTPS proxy at https://sandbox-proxy.agentbox.ru:65181.

The examples pass HTTPS_PROXY, HTTP_PROXY, and NO_PROXY only to the agent command and its child processes. These are process-wide network settings: other agent requests may also use the proxy. NO_PROXY keeps connections to localhost and 127.0.0.1 direct.

Compare price lists

The example uploads two supplier price lists as CSV files. Claude matches products by SKU and writes changes.md with price differences in RUB and percent, unchanged prices, and new items. In this dataset, A-100 increases by RUB 200 (20%), B-200 stays unchanged, and C-300 is new.

mjs
import { Sandbox } from "@abox-dev/sdk";

const apiKey = process.env.ANTHROPIC_API_KEY;
if (!apiKey) throw new Error("ANTHROPIC_API_KEY is required");
const proxy =
  process.env.SANDBOX_PROXY_URL ?? "https://sandbox-proxy.agentbox.ru:65181";
const sandbox = await Sandbox.create("claude", { timeoutMs: 600_000 });
try {
  await sandbox.files.makeDir("/home/user/work");
  await sandbox.files.write(
    "/home/user/work/prices-before.csv",
    "sku,price_rub\nA-100,1000\nB-200,2500\n",
  );
  await sandbox.files.write(
    "/home/user/work/prices-after.csv",
    "sku,price_rub\nA-100,1200\nB-200,2500\nC-300,800\n",
  );
  const handle = await sandbox.commands.run(
    "claude -p --model sonnet --dangerously-skip-permissions --output-format json",
    {
      cwd: "/home/user/work",
      timeoutMs: 300_000,
      background: true,
      stdin: true,
      envs: {
        ANTHROPIC_API_KEY: apiKey,
        HTTPS_PROXY: proxy,
        HTTP_PROXY: proxy,
        NO_PROXY: "localhost,127.0.0.1",
      },
      onStderr: (chunk) => process.stderr.write(chunk),
    },
  );
  await handle.sendStdin(
    "Compare prices-before.csv and prices-after.csv by sku. Write changes.md with price changes in RUB and percent, unchanged items, and new items. Use only the supplied data.",
  );
  await handle.closeStdin();
  const result = await handle.wait();
  const response = JSON.parse(result.stdout);
  if (response.is_error) throw new Error(response.result ?? response.subtype);
  console.log(response.result);
  console.log(
    "changes.md\n" + (await sandbox.files.read("/home/user/work/changes.md")),
  );
} finally {
  await sandbox.kill();
}

-p runs a task and exits. The application sends the task through stdin, closes the input stream, then waits for the result. --dangerously-skip-permissions bypasses Claude Code's permission checks and approval prompts. The agent uses its default toolset. The AgentBox sandbox provides the isolation boundary.

--output-format json returns response text in result, a session_id, and an is_error flag. The example checks that flag before reading the output file through the SDK. The finally block also deletes the sandbox on failure.

Read results and continue a session

The next example displays text as it is generated. Claude first compares the price lists, then receives a follow-up: add the purchase cost of 10 units of each existing product. History and files stay in the same sandbox.

mjs
import { Sandbox } from "@abox-dev/sdk";

const apiKey = process.env.ANTHROPIC_API_KEY;
if (!apiKey) throw new Error("ANTHROPIC_API_KEY is required");
const proxy =
  process.env.SANDBOX_PROXY_URL ?? "https://sandbox-proxy.agentbox.ru:65181";
const sandbox = await Sandbox.create("claude", { timeoutMs: 600_000 });
try {
  await sandbox.files.makeDir("/home/user/work");
  await sandbox.files.write(
    "/home/user/work/prices-before.csv",
    "sku,price_rub\nA-100,1000\nB-200,2500\n",
  );
  await sandbox.files.write(
    "/home/user/work/prices-after.csv",
    "sku,price_rub\nA-100,1200\nB-200,2500\nC-300,800\n",
  );
  async function execute(prompt, sessionId) {
    let pending = "";
    let result;
    let nextSessionId;
    function event(message) {
      if (
        message.type === "stream_event" &&
        message.event.delta?.type === "text_delta"
      ) {
        process.stdout.write(message.event.delta.text);
      }
      if (message.type === "result") result = message;
    }
    function onStdout(chunk) {
      pending += chunk;
      let newline;
      while ((newline = pending.indexOf("\n")) !== -1) {
        const line = pending.slice(0, newline);
        pending = pending.slice(newline + 1);
        if (line.trim()) event(JSON.parse(line));
      }
    }
    const handle = await sandbox.commands.run(
      "claude -p --model sonnet --dangerously-skip-permissions --output-format stream-json --verbose --include-partial-messages" +
        (sessionId ? ' --resume "$AGENT_SESSION_ID"' : ""),
      {
        cwd: "/home/user/work",
        timeoutMs: 300_000,
        background: true,
        stdin: true,
        envs: {
          ANTHROPIC_API_KEY: apiKey,
          HTTPS_PROXY: proxy,
          HTTP_PROXY: proxy,
          NO_PROXY: "localhost,127.0.0.1",
          AGENT_SESSION_ID: sessionId ?? "",
        },
        onStdout,
        onStderr: (chunk) => process.stderr.write(chunk),
      },
    );
    await handle.sendStdin(prompt);
    await handle.closeStdin();
    await handle.wait();
    if (pending.trim()) event(JSON.parse(pending));
    if (!result) throw new Error("Claude Code ended without a result");
    if (result.is_error)
      throw new Error(
        result.result ?? result.errors?.join("\n") ?? result.subtype,
      );
    nextSessionId = result.session_id;
    if (!nextSessionId)
      throw new Error("Claude Code did not return a session ID");
    console.log("\nSession:", nextSessionId);
    return nextSessionId;
  }
  const sessionId = await execute(
    "Compare prices-before.csv and prices-after.csv by sku. Write changes.md with price changes in RUB and percent, unchanged items, and new items.",
  );
  await execute(
    "Add a procurement summary to changes.md: calculate the cost of 10 units of each existing item before and after the change. Keep the original comparison.",
    sessionId,
  );
  console.log(
    "changes.md\n" + (await sandbox.files.read("/home/user/work/changes.md")),
  );
} finally {
  await sandbox.kill();
}

--output-format stream-json --verbose --include-partial-messages enables JSONL output. The handler buffers stdout chunks into complete lines and prints stream_eventevent.delta.text when the delta type is text_delta.

The final result event contains is_error and session_id. Once the command succeeds, the application passes this ID to --resume and sends the next task through stdin. The second process continues the same conversation. The sandbox is deleted after reading the updated changes.md.

For this data, the cost of 10 units each of A-100 and B-200 increases from RUB 35,000 to RUB 37,000. The new C-300 item stays in the comparison but is excluded from this calculation. See the Claude Code guide for other options.