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

# Headless Claude Code automation

> Drive Claude Code non-interactively from Gravix Layer with -p, JSON output, streaming events, and session resume.

Once a Claude runtime can reach Anthropic ([Get started](/guides/claude-code/get-started)), treat `claude` as a **worker**: your app or CI submits a prompt, Claude edits files inside the sandbox, and you collect stdout / artifacts.

## Prerequisites

Reuse an Identity provider + network policy from get-started, or create them inline as below.

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
export GRAVIXLAYER_API_KEY="your-gravixlayer-api-key"
export ANTHROPIC_API_KEY="sk-ant-..."
export GRAVIXLAYER_CLAUDE_TEMPLATE="agent-claude"
# Optional if already created:
# export ANTHROPIC_PROVIDER_ID=...
# export CLAUDE_NETWORK_POLICY_ID=...
```

## Permission model

For unattended runs:

```text theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
claude --dangerously-skip-permissions -p "..."
```

Auto-approval is contained by the Gravix microVM. Keep [network allowlists](/documentation/agentruntime/getting-started/network-policies) tight and cap `timeout` on every `run_cmd` / `runtime exec`. The Claude template sets `IS_SANDBOX=1` so bypass mode is allowed.

## Minimal driver

Prefer `command=` + `args=` so user-supplied prompts cannot break shell quoting.

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  import os
  import uuid

  from gravixlayer import GravixLayer

  TEMPLATE = os.environ["GRAVIXLAYER_CLAUDE_TEMPLATE"]
  ANTHROPIC_API_KEY = os.environ["ANTHROPIC_API_KEY"]

  client = GravixLayer()

  provider_id = os.environ.get("ANTHROPIC_PROVIDER_ID")
  policy_id = os.environ.get("CLAUDE_NETWORK_POLICY_ID")

  if not provider_id:
      provider = client.identity.providers.create(
          name=f"anthropic-{uuid.uuid4().hex[:8]}",
          provider_type="api_key",
          secrets=[{"key": "ANTHROPIC_API_KEY", "value": ANTHROPIC_API_KEY}],
      )
      provider_id = provider.id

  if not policy_id:
      policy = client.network_policies.create(
          name=f"claude-egress-{uuid.uuid4().hex[:8]}",
          egress_mode="allowlist",
          rules=[
              {"destination": "api.anthropic.com", "port": 443, "protocol": "tcp"},
          ],
      )
      policy_id = policy.id

  runtime = client.runtime.create(
      template=TEMPLATE,
      providers=[provider_id],
      network_policy_ids=[policy_id],
      env_vars={"ANTHROPIC_API_KEY": ANTHROPIC_API_KEY},
      timeout=1800,
  )

  prompt = (
      "Create a hello-world HTTP server in Go under /workspace/hello "
      "and print the file tree when done."
  )

  result = runtime.run_cmd(
      command="claude",
      args=[
          "--dangerously-skip-permissions",
          "-p",
          prompt,
          "--output-format",
          "text",
      ],
      working_dir="/workspace",
      timeout=900,
  )
  print(result.stdout)
  print("exit_code:", result.exit_code)

  tree = runtime.run_cmd(
      command="find",
      args=["/workspace/hello", "-type", "f"],
      timeout=60,
  )
  print(tree.stdout)

  runtime.kill()
  ```

  ```bash CLI theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  # Assumes PROVIDER_ID and POLICY_ID already exist (see get-started)
  RT=$(gravixlayer runtime create \
    --template "$GRAVIXLAYER_CLAUDE_TEMPLATE" \
    --provider "$ANTHROPIC_PROVIDER_ID" \
    --network-policy "$CLAUDE_NETWORK_POLICY_ID" \
    --env "ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY" \
    --timeout 1800 \
    --wait \
    --output json | jq -r '.runtime_id')

  gravixlayer runtime exec "$RT" --timeout 900 --workdir /workspace -- \
    claude --dangerously-skip-permissions \
    -p 'Create a hello-world HTTP server in Go under /workspace/hello' \
    --output-format text

  gravixlayer runtime exec "$RT" -- find /workspace/hello -type f
  gravixlayer runtime kill "$RT" -y
  ```

  ```bash cURL theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  API="https://api.gravixlayer.ai/v1"
  AUTH=(-H "Authorization: Bearer $GRAVIXLAYER_API_KEY" -H "Content-Type: application/json")

  RT=$(curl -sS -X POST "$API/agents/runtime" "${AUTH[@]}" \
    -d "{
      \"template\": \"$GRAVIXLAYER_CLAUDE_TEMPLATE\",
      \"cloud\": \"azure\",
      \"region\": \"eastus2\",
      \"timeout\": 1800,
      \"providers\": [\"$ANTHROPIC_PROVIDER_ID\"],
      \"network_policy_ids\": [\"$CLAUDE_NETWORK_POLICY_ID\"],
      \"env_vars\": {\"ANTHROPIC_API_KEY\": \"$ANTHROPIC_API_KEY\"}
    }" | jq -r '.runtime_id')

  # Wait for running (same loop as get-started), then:
  curl -sS -X POST "$API/agents/runtime/$RT/commands/run" "${AUTH[@]}" \
    -d '{
      "command": "claude",
      "args": [
        "--dangerously-skip-permissions",
        "-p", "Create a hello-world HTTP server in Go under /workspace/hello",
        "--output-format", "text"
      ],
      "working_dir": "/workspace",
      "timeout": 900000
    }' | jq .

  curl -sS -X DELETE "$API/agents/runtime/$RT" \
    -H "Authorization: Bearer $GRAVIXLAYER_API_KEY"
  ```
</CodeGroup>

## Structured JSON + session resume

`--output-format json` returns machine-readable output including `session_id`. Keep the **same runtime** and pass `--resume` for follow-ups.

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import json
import os

from gravixlayer import GravixLayer

client = GravixLayer()
runtime = client.runtime.create(
    template=os.environ["GRAVIXLAYER_CLAUDE_TEMPLATE"],
    providers=[os.environ["ANTHROPIC_PROVIDER_ID"]],
    network_policy_ids=[os.environ["CLAUDE_NETWORK_POLICY_ID"]],
    env_vars={"ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"]},
    timeout=1800,
)

first = runtime.run_cmd(
    command="claude",
    args=[
        "--dangerously-skip-permissions",
        "--output-format",
        "json",
        "-p",
        "Analyze /workspace and propose a one-step refactor plan.",
    ],
    timeout=900,
)
assert first.success, first.stderr
payload = json.loads(first.stdout)
session_id = payload["session_id"]
print("session_id:", session_id)

follow = runtime.run_cmd(
    command="claude",
    args=[
        "--dangerously-skip-permissions",
        "--resume",
        session_id,
        "-p",
        "Implement step 1 of that plan.",
    ],
    timeout=900,
)
print(follow.stdout)
print("exit_code:", follow.exit_code)

runtime.kill()
```

## Streaming events (`stream-json`)

Use Claude’s `stream-json` format with Gravix Layer’s `on_stdout` callback (SSE under the hood). The SDK still returns aggregated `stdout` / `exit_code`.

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import json
import os

from gravixlayer import GravixLayer

client = GravixLayer()
runtime = client.runtime.create(
    template=os.environ["GRAVIXLAYER_CLAUDE_TEMPLATE"],
    providers=[os.environ["ANTHROPIC_PROVIDER_ID"]],
    network_policy_ids=[os.environ["CLAUDE_NETWORK_POLICY_ID"]],
    env_vars={"ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"]},
    timeout=1800,
)

def on_stdout(chunk: str) -> None:
    for line in chunk.splitlines():
        if not line.strip():
            continue
        try:
            event = json.loads(line)
        except json.JSONDecodeError:
            print(line, end="")
            continue
        kind = event.get("type")
        if kind == "assistant":
            usage = (event.get("message") or {}).get("usage") or {}
            print("assistant tokens:", usage.get("output_tokens"))
        elif kind == "result":
            print("done:", event.get("subtype"), "ms=", event.get("duration_ms"))

result = runtime.run_cmd(
    command="claude",
    args=[
        "--dangerously-skip-permissions",
        "--output-format",
        "stream-json",
        "-p",
        "List the top-level files under /workspace.",
    ],
    timeout=900,
    on_stdout=on_stdout,
)
print("exit_code:", result.exit_code)

runtime.kill()
```

CLI streaming:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
gravixlayer runtime exec "$RT" --stream --timeout 900 -- \
  claude --dangerously-skip-permissions \
  --output-format stream-json \
  -p 'List the top-level files under /workspace'
```

## Patterns

| Pattern          | Approach                                                                           |
| ---------------- | ---------------------------------------------------------------------------------- |
| One-shot task    | Single `run_cmd` with `-p` and `--output-format text`                              |
| Pipeline / CI    | `--output-format json`, assert `success`, download artifacts via `runtime.file`    |
| Multi-turn agent | Same runtime + `--resume <session_id>`                                             |
| Parallel tasks   | Separate runtimes — [Long-running agents](/guides/claude-code/long-running-agents) |

## Next

* [Repo & git workflows](/guides/claude-code/repo-workflows)
* [Long-running & parallel agents](/guides/claude-code/long-running-agents)
