--- title: "Remote Claude Code Sessions" description: "Run Claude Code in a sandboxed Baponi environment with automatic credential injection for Anthropic API keys and GitHub access. Clone repos, run headless agent sessions, and push results, all fully isolated." url: https://baponi.ai/docs/guides/claude-code lastUpdated: 2026-03-06 --- # Remote Claude Code Sessions Run Claude Code as a remote headless agent inside an isolated Baponi sandbox. Credentials for Anthropic and GitHub are injected automatically through [connectors](/features#connectors). Your code never handles API keys or tokens directly. ## Prerequisites 1. **Baponi account and API key** Sign up at [console.baponi.ai](https://console.baponi.ai) and create an API key. 1,000 free credits/month, no credit card. 2. **Anthropic connector** In the Baponi admin UI, go to **Connectors → Add → Anthropic**. Paste your Anthropic API key. Attach the connector to the API key you'll use for executions. This injects `ANTHROPIC_API_KEY` as an environment variable into every sandbox execution, and Claude Code picks it up automatically. 3. **GitHub connector** (optional, for private repos and pushing) Go to **Connectors → Add → GitHub**. Paste a [fine-grained personal access token](https://github.com/settings/tokens?type=beta) with **Contents: Read and write** permission on the repos you want the agent to access. Attach it to the same API key. This injects a `.gitconfig` that transparently rewrites `https://github.com/` URLs to include your token, plus a `GH_TOKEN` environment variable for the `gh` CLI. `git clone`, `git push`, and `gh` commands work on private repos without any token handling in code. See [Connectors](/features#connectors) for the full list of supported integrations (databases, cloud storage, developer tools). 4. **Install the Python SDK** (optional, for the Python examples) ```bash pip install baponi ``` ## Quick start: one-shot agent task Run a single Claude Code task inside a sandbox: ```bash curl -X POST https://api.baponi.ai/v1/sandbox/execute \ -H "Authorization: Bearer $BAPONI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "code": "claude -p \"Research the current price of Bitcoin and summarize your findings\" --dangerously-skip-permissions", "language": "bash", "timeout": 300 }' ``` ```python from baponi import Baponi client = Baponi() result = client.execute( code='claude -p "Research the current price of Bitcoin and summarize your findings" --dangerously-skip-permissions', language="bash", timeout=300, ) print(result.stdout) ``` Claude Code runs inside the sandbox with full system access: it can make HTTP requests, install packages, read and write files, and run code. ## Autonomous GitHub workflow With both the Anthropic and GitHub connectors attached, Claude Code can autonomously discover repositories, make changes, and push, all within a single sandbox execution. ```python from baponi import Baponi client = Baponi() result = client.execute( code='''claude -p "List the GitHub repos available to you. Pick the least important one you can write to. Add a '## Fun Fact' section to its README with a funny sandbox coding joke. Commit and push." \ --dangerously-skip-permissions \ --max-turns 20''', language="bash", timeout=600, thread_id="github-agent-session", ) print(result.stdout) ``` Claude Code handles the entire workflow: it calls `gh repo list` to discover available repositories, evaluates which one to target, clones it, reads the README, makes the edit, commits, and pushes, all with credentials injected transparently through [connectors](/features#connectors). ## Multi-step workflow: clone, agent, push For more control, split the workflow into persistent steps using `thread_id`. Each call builds on the previous one. Cloned repos, installed packages, and generated files all persist. ```python from baponi import Baponi client = Baponi() thread = "feature-auth-refactor" # Step 1: Clone the repo (uses GitHub connector credentials automatically) clone = client.execute( code="git clone https://github.com/your-org/your-repo.git /home/baponi/repo", language="bash", timeout=60, thread_id=thread, ) assert clone.exit_code == 0, f"Clone failed: {clone.stderr}" # Step 2: Run Claude Code on the codebase agent = client.execute( code='''cd /home/baponi/repo && claude -p "Refactor the auth module to use async/await. Update all callers. Run the test suite and fix any failures." \ --dangerously-skip-permissions \ --output-format stream-json \ --max-turns 50''', language="bash", timeout=3600, # 1 hour for complex tasks thread_id=thread, ) print(agent.stdout) # Step 3: Push the changes push = client.execute( code='''cd /home/baponi/repo \ && git add -A \ && git commit -m "refactor: migrate auth module to async/await" \ && git push origin HEAD''', language="bash", timeout=60, thread_id=thread, ) assert push.exit_code == 0, f"Push failed: {push.stderr}" ``` Each step uses the same `thread_id`, so the cloned repo from step 1 is still on disk when step 2 runs Claude Code, and the changes from step 2 are still there when step 3 pushes. ## Claude Code flags reference Key flags for headless sandbox execution: | Flag | Purpose | |------|---------| | `-p "prompt"` | Non-interactive mode. Runs the prompt and exits. **Required in sandboxes.** | | `--dangerously-skip-permissions` | Auto-approve all tool calls. **Required in sandboxes** (no TTY for prompts). | | `--output-format stream-json` | Structured JSON output for parsing agent actions programmatically. | | `--max-turns 50` | Safety rail: stop after N tool-use turns to prevent runaway loops. | | `--max-budget-usd 10.00` | Cost ceiling for the Anthropic API calls within the session. | | `--allowedTools "Bash,Read,Edit,Write"` | Pre-approve specific tools (alternative to `--dangerously-skip-permissions`). | ## Using the Claude Agent SDK The `claude-agent-sdk` gives you programmatic control over the agent loop. Your backend acts as the control plane, approving or denying every tool call while the agent runs isolated in a Baponi sandbox. The Claude Agent SDK ships a bundled binary with its own internal sandbox runtime. Because the code is already running inside a Baponi sandbox, the SDK's internal sandbox triggers operations that are blocked by Baponi's security policy, and the outer sandbox prevents the inner one from initializing. Set `cli_path` to the pre-installed Node.js CLI to bypass this. All SDK features (hooks, streaming, tool approval) work identically: ```python options = ClaudeAgentOptions( cli_path="/home/baponi/.local/bin/claude", ) ``` The example below runs a minimal Flask control plane inside the sandbox to demonstrate the pattern. In production, your control plane is a server you run anywhere (your own infrastructure, a cloud function, etc.) and the SDK hooks call out to it over the network. Since `flask` is not in the default image, install it first. Using the same `thread_id` across calls means installed packages and files persist between steps. The control plane server below is a minimal example that approves every tool call. In production, this would be your own server running anywhere outside the sandbox, enforcing whatever approval policy you need: ```python title="control_plane.py" from flask import Flask, request, jsonify app = Flask(__name__) @app.post("/approve-tool") def approve(): tool = request.json["tool"] print(f"[control-plane] Approved: {tool}") return jsonify(approved=True) # Your policy logic goes here if __name__ == "__main__": app.run(host="127.0.0.1", port=5100) ``` The agent script starts the control plane in the background, then runs the SDK with a `PreToolUse` hook that routes every tool call through it for approval: ```python title="agent.py" from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions from claude_agent_sdk.types import ( AssistantMessage, ResultMessage, TextBlock, HookInput, HookMatcher, ) # Start the control plane in the background server = subprocess.Popen(["python3", "/home/baponi/control_plane.py"]) time.sleep(2) async def approve_hook(input_data, tool_use_id, context): async with httpx.AsyncClient() as http: resp = await http.post("http://127.0.0.1:5100/approve-tool", json={ "tool": input_data["tool_name"], "input": input_data["tool_input"], }) if not resp.json().get("approved"): return {"hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", }} return {} async def main(): options = ClaudeAgentOptions( cli_path="/home/baponi/.local/bin/claude", allowed_tools=["Bash", "Read", "Write", "Edit"], hooks={"PreToolUse": [HookMatcher(matcher=".*", hooks=[approve_hook])]}, ) async with ClaudeSDKClient(options=options) as sdk: await sdk.query("Write a haiku to /home/baponi/haiku.txt, then read it back") async for msg in sdk.receive_response(): if isinstance(msg, AssistantMessage): for block in msg.content: if isinstance(block, TextBlock): print(block.text) elif isinstance(msg, ResultMessage): break asyncio.run(main()) server.terminate() ``` Tie it together with the Baponi SDK. Write both files to the sandbox, then run the agent: ```python from baponi import Baponi client = Baponi() thread = "sdk-controlled-session" # Install Flask (persists via thread_id) client.execute(code="pip install flask", language="bash", timeout=60, thread_id=thread) # Write both scripts to the persistent filesystem CONTROL_PLANE_PY = "..." # Contents of control_plane.py above AGENT_PY = "..." # Contents of agent.py above client.execute( code=f"import pathlib; pathlib.Path('/home/baponi/control_plane.py').write_text('''{CONTROL_PLANE_PY}''')", language="python", timeout=10, thread_id=thread, ) client.execute( code=f"import pathlib; pathlib.Path('/home/baponi/agent.py').write_text('''{AGENT_PY}''')", language="python", timeout=10, thread_id=thread, ) # Run the agent result = client.execute( code="python3 /home/baponi/agent.py", language="bash", timeout=300, thread_id=thread, ) print(result.stdout) ``` Every tool call (`Write`, `Read`, `Bash`) hits `POST /approve-tool` before execution. The sandbox handles isolation; your backend handles policy. ## How connector credential injection works Baponi [connectors](/features#connectors) inject credentials into the sandbox at execution time without exposing them in your code: | Connector | What gets injected | How Claude Code uses it | |-----------|-------------------|------------------------| | **Anthropic** | `ANTHROPIC_API_KEY` env var | Claude Code reads it automatically for API calls | | **GitHub** | `.gitconfig` with URL rewriting + `GH_TOKEN` env var | `git clone/push` and `gh` CLI work on private repos transparently | Your wrapper code never touches credentials. Configure them once in the admin UI, attach to your API key, and every execution gets them automatically. See [Connectors](/features#connectors) for supported integrations including PostgreSQL, MySQL, BigQuery, AWS S3, GCS, and more. ## FAQ ### What timeout should I set? For simple tasks (write a function, fix a bug), 300 seconds (5 minutes) is usually enough. For complex multi-file refactors, set up to 3600 seconds (1 hour). The Pro plan supports timeouts up to 3600s. ### Can I run multiple Claude Code sessions in parallel? Yes. Use different `thread_id` values for each session. Each gets its own persistent filesystem. Baponi handles concurrency, and there's no shared state between threads. ### What happens if Claude Code exceeds the timeout? The sandbox terminates the process and returns what was captured so far. Use `--max-turns` as an additional safety rail to prevent Claude Code from running too many iterations. ### Why does the Agent SDK need `cli_path`? The Claude Agent SDK's bundled binary includes its own internal sandbox runtime (bubblewrap) for local development safety. Inside a Baponi sandbox, the code is already running in an isolated environment with multi-layer security, so the SDK's internal sandbox is redundant. When it tries to initialize, it triggers low-level operations that conflict with Baponi's security policy. Setting `cli_path` to the Node.js CLI (`/home/baponi/.local/bin/claude`) bypasses this entirely. The Node.js CLI provides the exact same functionality (hooks, streaming, tool approval) without the internal sandbox layer. --- More: [Documentation index](https://baponi.ai/docs.md) · [Pricing](https://baponi.ai/pricing) · Site index: [llms.txt](https://baponi.ai/llms.txt)