> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreweave.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Run a coding agent in a development sandbox

> Build a long-lived sandbox that runs Claude Code, then reattach to it from your terminal.

Most sandboxes are short-lived: an agent framework or evaluation harness creates one, runs a task, and discards it. A development sandbox, or dev box, inverts that. You create one sandbox deliberately, keep it for hours, and work inside it from your terminal.

This tutorial builds a dev box that runs [Claude Code](https://docs.claude.com/en/docs/claude-code/overview) against a repository. It then shows you how to reattach to the same sandbox from a shell and retrieve the results.

<Note>
  CoreWeave sandboxes are in public preview. For access, contact your CoreWeave account team, [CoreWeave Support](https://cloud.coreweave.com/contact), or email [support@coreweave.com](mailto:support@coreweave.com).
</Note>

## What you build

By the end of this tutorial, you have all of the following:

* A sandbox with an explicit multi-hour lifetime and outbound internet access.
* Claude Code installed and authenticated inside it.
* A shell attached to the running sandbox from your own terminal.
* The agent's output copied back to your machine.

The most important step is the lifetime. A sandbox created without `max_lifetime_seconds` runs for at most 10 minutes, which is enough for a batch task and far too short for a dev box.

## Prerequisites

Before you start, make sure you have the following:

* A sandbox runner in the `Ready` state. To deploy one, see [Get started with CoreWeave sandboxes](/products/sandboxes/get-started).
* A CoreWeave API access token exported as `CWSANDBOX_API_KEY`.
* An Anthropic API key for Claude Code. Get one from the [Anthropic Console](https://console.anthropic.com/).
* The `cwsandbox` SDK with the CLI extra installed. The CLI ships separately from the core SDK, and you need it to reattach to the sandbox later:

  ```bash theme={"system"}
  uv pip install "cwsandbox[cli]"
  ```

Set both credentials in your shell. Replace `[COREWEAVE-API-TOKEN]` with the **Token Secret** from the [Tokens](https://console.coreweave.com/tokens) page, and `[ANTHROPIC-API-KEY]` with your Anthropic key:

```bash theme={"system"}
export CWSANDBOX_API_KEY="[COREWEAVE-API-TOKEN]"
export ANTHROPIC_API_KEY="[ANTHROPIC-API-KEY]"
```

## Step 1: Find a profile with internet access

Claude Code calls the Anthropic API from inside the sandbox, so the sandbox needs outbound internet access. Sandboxes don't get it by default: profiles determine which egress modes are available, and many deny all outbound traffic.

List the profiles that permit internet egress:

```python theme={"system"}
import cwsandbox

profiles = cwsandbox.list_profiles(egress_mode="internet")

for p in profiles:
    egress = ", ".join(m.name for m in p.egress_modes)
    print(f"{p.profile_name}  runner={p.runner_id}  egress: {egress}")
```

```text title="Example output" theme={"system"}
dev-internet  runner=runner-7f3a91c2  egress: internet, isolated
```

If the list is empty, no profile you can reach allows internet egress. Ask whoever administers your runners to add one. For the guardrails involved, see [Configure a sandbox profile](/products/sandboxes/profiles/configure).

Note the profile name you want. The next step passes it explicitly so the sandbox doesn't use a deny-all profile.

<Note>
  Egress mode names come from your profile configuration, not from a fixed list. `internet` is the conventional name for full outbound access, but your organization might use a different one. The names in `p.egress_modes` are the values you can pass.
</Note>

## Step 2: Create the sandbox with an explicit lifetime

Set three things when you create the sandbox: a lifetime long enough for a working session, internet egress, and your Anthropic key as an environment variable.

Claude Code requires Node.js, so this tutorial starts from a `node` image. Replace `[PROFILE-NAME]` with the profile from Step 1:

```python title="create_devbox.py" theme={"system"}
import os

from cwsandbox import NetworkOptions, ResourceOptions, Sandbox

sandbox = Sandbox.run(
    container_image="node:22",
    profile_names=["[PROFILE-NAME]"],
    max_lifetime_seconds=14400,  # 4 hours
    network=NetworkOptions(egress_mode="internet"),
    environment_variables={"ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"]},
    resources=ResourceOptions(
        requests={"cpu": "2", "memory": "4Gi"},
        limits={"cpu": "4", "memory": "8Gi"},
    ),
    tags=["dev-box"],
)

sandbox.wait()
print(f"Sandbox ready: {sandbox.sandbox_id}")
```

```text title="Example output" theme={"system"}
Sandbox ready: sandbox-4c81e0a7
```

Record the sandbox ID. You need it to reattach from another terminal.

This example deliberately avoids a `with` block. A context manager stops the sandbox when the block exits. That's the right behavior for a batch job and the wrong behavior for a dev box you want to keep.

The sandbox also outlives the script that created it. The SDK's exit and signal handlers stop sandboxes that belong to a [`Session`](/products/sandboxes/client/guides/sessions), and `Sandbox.run()` doesn't create one, so this sandbox keeps running after the Python process ends. That's what makes the next steps possible from a separate terminal. It also means the lifetime you set is the only thing that eventually cleans it up.

<Warning>
  Choose the lifetime now, because you can't change it later. No API extends the lifetime of a running sandbox. When the lifetime expires, the platform terminates the sandbox immediately, without a grace period and without saving its filesystem. Anything you haven't copied out is lost. Pick a value that comfortably covers your session, and copy work out as you go.
</Warning>

## Step 3: Install Claude Code

Install the Claude Code CLI inside the running sandbox:

```python theme={"system"}
result = sandbox.exec(
    ["npm", "install", "-g", "@anthropic-ai/claude-code"],
    timeout_seconds=300,
).result()
print(result.returncode)
```

Installing at runtime keeps this tutorial self-contained, but it costs a minute or two on every new sandbox. If you create dev boxes regularly, build an image with Claude Code and your other tools already installed. Publish it to a registry your runner can pull from, then pass it as `container_image` instead.

Confirm the CLI is on the path and can access its credentials:

```python theme={"system"}
result = sandbox.exec(["claude", "--version"]).result()
print(result.stdout)
```

```text title="Example output" theme={"system"}
2.0.44 (Claude Code)
```

## Step 4: Give the agent something to work on

A dev box requires a workspace. Clone a repository into the sandbox, then let the agent work on it. Replace `[REPOSITORY-URL]` with the URL of the repository you want to clone:

```python theme={"system"}
sandbox.exec(
    ["git", "clone", "[REPOSITORY-URL]", "/workspace"],
    timeout_seconds=300,
).result()
```

Run Claude Code non-interactively with `-p`, which sends a single prompt and prints the response:

```python theme={"system"}
result = sandbox.exec(
    ["claude", "-p", "Summarize the architecture of this project in 10 bullet points."],
    cwd="/workspace",
    timeout_seconds=600,
).result()
print(result.stdout)
```

Give agent commands a generous `timeout_seconds`. The value bounds how long the client waits for that one command, and an agent working through a large repository can run for several minutes. Keep it below the sandbox's remaining lifetime, because a command outliving its sandbox fails with it.

To continue the same conversation across separate `exec()` calls, pass `--continue`. This example also has the agent write its answer to a file, which later steps read back:

```python theme={"system"}
result = sandbox.exec(
    ["claude", "-p", "--continue", "List the three riskiest files to change. Write them to notes.md."],
    cwd="/workspace",
    timeout_seconds=600,
).result()
print(result.stdout)
```

The agent now leaves `/workspace/notes.md` behind in the sandbox. Step 6 copies it back to your machine.

## Step 5: Reattach from your terminal

The `cwsandbox` CLI opens an interactive shell in a running sandbox from any terminal, including a different machine from the one that created it. Driving an agent through `exec()` calls works, but a dev box is more useful when you can work in it directly.

Replace `[SANDBOX-ID]` with the ID from Step 2:

```bash theme={"system"}
cwsandbox sh [SANDBOX-ID]
```

You get a TTY inside the sandbox, where you can run `claude` interactively:

```text title="Example output" theme={"system"}
root@sandbox-4c81e0a7:/# cd /workspace && claude
```

Exit the shell with `exit` or Ctrl+D. Exiting the shell doesn't stop the sandbox, so you can reconnect as often as you like while the lifetime lasts.

Two other CLI commands are useful here:

```bash theme={"system"}
# List your running sandboxes
cwsandbox ls

# Run a single command without opening a shell
cwsandbox exec [SANDBOX-ID] cat /workspace/notes.md
```

<Note>
  A single shell session ends after 24 hours, even when the sandbox's lifetime is longer. The sandbox keeps running, so if a long-lived session drops, reconnect with `cwsandbox sh`.
</Note>

## Step 6: Retrieve your work and stop the sandbox

Copy anything you want to keep back to your machine before the sandbox stops. `read_file()` returns bytes:

```python theme={"system"}
notes = sandbox.read_file("/workspace/notes.md").result()

with open("notes.md", "wb") as f:
    f.write(notes)
```

To send a file the other way, use `write_file()`, which also takes bytes:

```python theme={"system"}
sandbox.write_file("/workspace/prompt.txt", b"Refactor the client module.").result()
```

When you finish, stop the sandbox explicitly rather than waiting for the lifetime to expire. A `stop()` gives processes inside the sandbox a grace period to exit cleanly, which expiry does not:

```python theme={"system"}
sandbox.stop().result()
```

If you've lost the Python object, reattach by ID first, then stop it:

```python theme={"system"}
from cwsandbox import Sandbox

sandbox = Sandbox.from_id("[SANDBOX-ID]").result()
sandbox.stop().result()
```

To find dev boxes you've lost track of entirely, use `Sandbox.list()` with the `dev-box` tag from Step 2. For more information, see [Cleanup patterns](/products/sandboxes/client/guides/cleanup-patterns).

## What to know before you rely on a dev box

A few behaviors limit what a dev box can do:

* **The lifetime is fixed at creation.** No extend operation exists. To keep working past the limit, copy your work out, then create a new sandbox with a longer lifetime.
* **Expiry is not a clean shutdown.** The platform terminates the sandbox and its filesystem goes with it. Treat the sandbox as scratch space and keep anything durable in a repository or copied to your machine.
* **A reattached handle doesn't report the remaining lifetime.** A sandbox retrieved through `Sandbox.from_id()` or `Sandbox.list()` doesn't carry its lifetime, so track when you created it yourself.
* **Credentials injected as environment variables are frozen at start.** The sandbox's `ANTHROPIC_API_KEY` is set once when the container starts and can't be rotated in place. To change it, create a new sandbox.
* **Sandboxes have no idle timeout.** Nothing stops a sandbox because you stopped using it. Only the lifetime or an explicit `stop()` ends it, so an unused dev box keeps consuming resources until its lifetime expires.

## Next steps

* [Timeouts](/products/sandboxes/client/guides/sandbox-configuration#timeouts) covers all four timeout settings and how they interact.
* [Sandbox lifecycle](/products/sandboxes/client/guides/sandbox-lifecycle) explains sandbox states, waiting, and shutdown in depth.
* [Interactive shells and TTY](/products/sandboxes/client/guides/interactive-shells) shows how to drive a TTY session from Python instead of the CLI.
* [Discovery](/products/sandboxes/client/guides/discovery) covers filtering runners and profiles by capability and capacity.
* [Cleanup patterns](/products/sandboxes/client/guides/cleanup-patterns) shows how to find and stop sandboxes you've lost track of.
