Use a claude and gemini compatible agentic sandbox#70
Conversation
Use OAuth authentication rather than an API key to reduce costs.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| [ | ||
| "docker", | ||
| "build", | ||
| "--build-arg", | ||
| f"AGENT={args.agent}", | ||
| "-t", | ||
| image_name, | ||
| "-f", | ||
| os.path.join(project_root, "docker/Dockerfile"), | ||
| project_root, | ||
| ] |
There was a problem hiding this comment.
docker build is passing --build-arg AGENT=..., but the Dockerfile doesn't use AGENT anywhere, so the build arg (and ARG AGENT=gemini) are currently redundant and can confuse readers about what varies between the ...-gemini-sandbox and ...-claude-sandbox images. Either remove the build arg/ARG entirely, or actually use it (e.g., to conditionally install only the selected CLI) so the tag difference reflects a real image difference.
| Use `--rebuild-docker` to rebuild the container image and `--update-claude` to | ||
| update the Claude Code CLI inside the container. | ||
| where `<agent>` is either `claude` or `gemini`. | ||
|
|
There was a problem hiding this comment.
The sandbox docs no longer mention API keys (good), but they also don't explain the new OAuth-based auth flow (e.g., that the script mounts ~/.gemini / ~/.claude{,.json} from the host and that first-run will prompt a login). Adding a brief note here would prevent users from thinking the sandbox is broken when the agent asks for auth.
| Authentication uses the agent CLI's OAuth-based login flow rather than API | |
| keys. The sandbox mounts your existing host authentication state (for example, | |
| `~/.gemini` and `~/.claude` / `~/.claude.json`) into the container. On first | |
| run, if you have not already authenticated locally, the agent may prompt you | |
| to sign in; this is expected and does not indicate that the sandbox is broken. |
| fd = os.open( | ||
| trusted_folders_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600 | ||
| ) | ||
| os.write(fd, b'{"/workspace": "TRUST_FOLDER"}') | ||
| os.close(fd) | ||
| except FileExistsError: | ||
| pass | ||
| settings_path = os.path.join(gemini_config_dir, "settings.json") | ||
| try: | ||
| fd = os.open(settings_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) | ||
| os.write(fd, b'{"selectedAuthType": "oauth-personal"}') | ||
| os.close(fd) | ||
| except FileExistsError: |
There was a problem hiding this comment.
The config seeding uses os.open/os.write/os.close in-line; if os.write raises, the fd won't be closed. Consider wrapping the fd in a context manager (or using try/finally) and extracting a small helper to reduce duplication between the two file-creation blocks.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| args = parser.parse_args() | ||
|
|
||
| def log(msg: str) -> None: | ||
| """Log a message if verbose mode is enabled.""" |
There was a problem hiding this comment.
The Docker image name is agent-specific (cc-protocol-{agent}-sandbox), but the Dockerfile installs both CLIs unconditionally. After building one image, running the other agent will fail unless its separate image is also built, and you end up with duplicated identical images. Consider either using a single shared image name (since both CLIs are present) or making the Dockerfile conditional on AGENT so per-agent images are meaningful.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try: | ||
| os.write(fd, content) | ||
| finally: | ||
| os.close(fd) |
There was a problem hiding this comment.
os.write() is not guaranteed to write the full buffer; this can result in truncated JSON in the seeded config files. Consider writing via os.fdopen(fd, 'wb') / .write() (or loop until all bytes are written) so the file contents are reliably complete.
| try: | |
| os.write(fd, content) | |
| finally: | |
| os.close(fd) | |
| with os.fdopen(fd, "wb") as file_obj: | |
| file_obj.write(content) |
| os.makedirs(host_claude_dir, mode=0o700, exist_ok=True) | ||
| # Ensure the file exists on the host so Docker doesn't create it as a directory. | ||
| # Use os.open with restrictive permissions to avoid exposing credentials to | ||
| # other local users on multi-user systems. |
There was a problem hiding this comment.
If ~/.claude.json already exists but is a directory (e.g., from a previous Docker run where the file path was mounted before being created), this will attempt to create/open it as a file and raise IsADirectoryError. Add an explicit os.path.isdir(host_claude_json) check and fail with a clear remediation message (or replace it) before proceeding.
| # other local users on multi-user systems. | |
| # other local users on multi-user systems. | |
| if os.path.isdir(host_claude_json): | |
| sys.exit( | |
| f"{host_claude_json} exists as a directory, but Claude expects this path " | |
| "to be a file. Remove or rename that directory and rerun this script so " | |
| "it can create an empty ~/.claude.json file." | |
| ) |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
@philipcraig have you tried it? |
|
I can't from here. The work laptop is locked down against non work LLM use. I'll be back in Monday |
|
Works on Windows |
Support OAuth authentication rather than an API key to reduce costs.