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. Your code never handles API keys or tokens directly.
Prerequisites
Section titled “Prerequisites”-
Baponi account and API key
Sign up at console.baponi.ai and create an API key. 1,000 free credits/month, no credit card.
-
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_KEYas an environment variable into every sandbox execution, and Claude Code picks it up automatically. -
GitHub connector (optional, for private repos and pushing)
Go to Connectors → Add → GitHub. Paste a fine-grained personal access token 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
.gitconfigthat transparently rewriteshttps://github.com/URLs to include your token, plus aGH_TOKENenvironment variable for theghCLI.git clone,git push, andghcommands work on private repos without any token handling in code.See Connectors for the full list of supported integrations (databases, cloud storage, developer tools).
-
Install the Python SDK (optional, for the Python examples)
Terminal window pip install baponi
Quick start: one-shot agent task
Section titled “Quick start: one-shot agent task”Run a single Claude Code task inside a sandbox:
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 }'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
Section titled “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.
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.
Multi-step workflow: clone, agent, push
Section titled “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.
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 codebaseagent = 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 changespush = 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
Section titled “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
Section titled “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 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:
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:
import asyncio, subprocess, timefrom claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptionsfrom claude_agent_sdk.types import ( AssistantMessage, ResultMessage, TextBlock, HookInput, HookMatcher,)import httpx
# Start the control plane in the backgroundserver = 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:
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 filesystemCONTROL_PLANE_PY = "..." # Contents of control_plane.py aboveAGENT_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 agentresult = 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
Section titled “How connector credential injection works”Baponi 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 for supported integrations including PostgreSQL, MySQL, BigQuery, AWS S3, GCS, and more.
What timeout should I set?
Section titled “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?
Section titled “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?
Section titled “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?
Section titled “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.