| name | sandbox |
| description | Run a Claude Code mission brief or task prompt inside an isolated Docker container with --dangerously-skip-permissions. Retrieves secrets from macOS Keychain, assembles a task-specific Dockerfile, mounts ~/.claude/ read-only, and uses a persistent named volume for resumability. Usage: /sandbox [session-name] [repo-url] [task-prompt or brief-path]
|
| disable-model-invocation | false |
| allowed-tools | Bash, Read, Write, Glob |
Model routing: Sonnet for implementation; Haiku for verification/scoring; Opus only for explicit architectural decisions.
Sandbox
Run a task or mission brief in an isolated Docker container with full Claude Code autonomy.
Phase 1 — Parse arguments
Parse $ARGUMENTS which may contain 1-3 parts in any order:
Document what was parsed before proceeding:
Parsed:
SESSION_NAME: auth-refactor
REPO_URL: https://github.com/org/repo.git
TASK_PROMPT: [first 120 chars of prompt or "(from file: path)"]
Phase 2 — Validate Keychain secrets
For each required secret, run:
security find-generic-password -a "$USER" -s "SECRET_NAME" -w 2>/dev/null
If a value is empty or the command returns non-zero, print the fix command and
STOP before running anything else:
Missing Keychain secret: SECRET_NAME
Fix: security add-generic-password -a "$USER" -s "SECRET_NAME" -w "your-value-here"
Required: GITHUB_ORG_TOKEN
API credentials (one set): ANTHROPIC_API_KEY OR all three
AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY + AWS_DEFAULT_REGION
Check all keys. For each missing key, print the fix command:
security add-generic-password -a "$USER" -s "KEY_NAME" -w "value"
Report all missing keys at once, then STOP.
Store all retrieved values as shell variables for use in Phase 7.
Phase 3 — Detect languages in target repo
If REPO_URL is a remote URL, clone it to a temp directory:
git clone --depth=1 "$REPO_URL" "/tmp/claude-sandbox-detect-$$"
DETECT_DIR="/tmp/claude-sandbox-detect-$$"
If REPO_URL is a local path or the current directory was used, set
DETECT_DIR to that path. Do not re-clone.
Check for these files to detect languages (accumulate into DETECTED_LANGS):
| File(s) | Language token |
|---|
pyproject.toml, requirements.txt, setup.py | python |
package.json, tsconfig.json | node |
*.csproj, *.sln | dotnet |
go.mod | go |
Cargo.toml | rust |
Report detected languages:
Detected languages: node python
If no languages are detected, DETECTED_LANGS is empty and only the base
layer will be used.
Phase 4 — Assemble Dockerfile
SESSION_NAME="${SESSION_NAME:-sandbox}"
DOCKERFILE="/tmp/claude-sandbox-${SESSION_NAME}.dockerfile"
cat ~/.claude/templates/Dockerfile.base > "$DOCKERFILE"
for lang in $DETECTED_LANGS; do
if [[ -f ~/.claude/templates/Dockerfile.$lang ]]; then
cat ~/.claude/templates/Dockerfile.$lang >> "$DOCKERFILE"
fi
done
echo "Assembled Dockerfile with layers: base $(echo $DETECTED_LANGS | tr ' ' ',')"
If ~/.claude/templates/Dockerfile.base does not exist, stop:
Missing template: ~/.claude/templates/Dockerfile.base
The sandbox requires this base template to build the container image.
Phase 5 — Build image
docker build \
-f "$DOCKERFILE" \
-t "claude-sandbox-${SESSION_NAME}" \
~/.claude
The build context is ~/.claude/ so that COPY templates/container-entrypoint.sh /entrypoint.sh (and any other template-relative COPY instructions) resolve
correctly.
If the build fails, stop immediately and print the full Docker error output. Do
not proceed to later phases.
Phase 6 — Create or reuse volumes
docker volume create "claude-sandbox-${SESSION_NAME}" 2>/dev/null || true
docker volume create "claude-sandbox-${SESSION_NAME}-meta" 2>/dev/null || true
echo "Volumes ready: claude-sandbox-${SESSION_NAME}, claude-sandbox-${SESSION_NAME}-meta"
The 2>/dev/null || true pattern means an already-existing volume is silently
reused — this is the resumability mechanism. Do not delete existing volumes.
Phase 7 — Run container
Print the docker run command with secrets redacted (***), then
execute with real values. The command shape:
docker run --rm \
-e REPO_URL="$REPO_URL" \
-e TASK_PROMPT="$TASK_PROMPT" \
-e GITHUB_ORG_TOKEN="$GITHUB_ORG_TOKEN" \
-e ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:-}" \
-e AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-}" \
-e AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-}" \
-e AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-}" \
--mount type=bind,source="$HOME/.claude",target=/root/.claude,readonly \
--mount type=volume,source="claude-sandbox-${SESSION_NAME}",target=/workspace \
--mount type=volume,source="claude-sandbox-${SESSION_NAME}-meta",target=/workspace-meta \
"claude-sandbox-${SESSION_NAME}"
Capture the exit code.
Phase 8 — Report
Based on EXIT_CODE:
-
Exit 0:
Sandbox completed successfully.
-
Non-zero:
Sandbox failed (exit $EXIT_CODE). Check /workspace-meta volume for sandbox.log.
Retrieve the last 50 lines of the log for inline display:
docker run --rm \
-v "claude-sandbox-${SESSION_NAME}-meta":/m \
alpine \
tail -n 50 /m/sandbox.log 2>/dev/null || echo "(sandbox.log not found)"
Notes
- Re-run
/sandbox session-name to resume an interrupted session — the named
volume preserves workspace state.
- To start fresh:
docker volume rm claude-sandbox-SESSION_NAME
- Full log:
docker run --rm -v claude-sandbox-SESSION_NAME-meta:/m alpine cat /m/sandbox.log
- Secrets are never written to disk or baked into the image — they are injected
at
docker run time only.
- Consider setting
sandbox.credentials in the sandboxed run's settings.json
(Claude Code v2.1.187+): it blocks the agent from reading credential files,
reinforcing security.md's secret-handling stance. Injected env-var secrets stay
available; on-disk credential files (e.g. ~/.aws/credentials, ~/.claude.json)
become unreadable — defense-in-depth for --dangerously-skip-permissions runs.
Verify the exact key name/shape against code.claude.com/docs/en/settings
before wiring it, since this skill mounts ~/.claude/ read-only.