feat: named sandbox templates with git checkout, environment, and bundled providers
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 8.7k
- Forks
- 1.3k
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 253
Description
Problem Statement
Sandbox creation currently requires users to manually specify environment variables, clone repos, and attach providers on every invocation. For repeatable workflows — such as running multiple parallel Claude Code sandboxes against the same repo with Vertex AI — this means re-typing the same flags each time, or scripting around the CLI.
Two concrete pain points:
-
No git checkout on init. Users who want code in a sandbox must either
--uploadlocal files or manuallyexecagit cloneafter creation. There is no way to declaratively say "this sandbox starts with repo X checked out." -
Provider-adjacent env vars have no home. If a generic ADC/Google metadata provider were to replace the current
google-vertex-aiprovider (see discussion), Anthropic-specific Vertex env vars (CLAUDE_CODE_USE_VERTEX,ANTHROPIC_VERTEX_PROJECT_ID, etc.) would no longer belong in the provider's resolution logic — they are consumer config that sits on top of the credential layer. Today there is nowhere to put them except manualexecor custom images.
A named sandbox template stored on the gateway solves both: define once, create many sandboxes from the same template, with environment, git checkout, and default providers bundled together. Templates are purely a convenience — users can always create sandboxes without one, specifying flags inline as they do today.
Related Issues
- #1520 —
feat(cli): investigate sandbox specs and openshell apply -f. Investigates file-based declarative sandbox definitions. Complementary: a YAML file could be the authoring format for a named template, andapply -fcould create/update templates on the gateway. - #863 —
SandboxTemplate.environmentenv vars not applied to container. Bug: the existingSandboxTemplate.environmentproto field doesn't flow through to the sandbox. Must be fixed before templates can set env vars. - #1447 —
feat: warmpool support for OpenShell sandboxes. Warm pools need a specification for what to pre-create — named templates are a natural fit. Note: #1447 references theagents.x-k8s.ioSandboxTemplateCRD, which is a Kubernetes-level pod templating concept (image, resources, volumes). TheNamedSandboxTemplateproposed here is an OpenShell gateway-scoped concept at a higher abstraction level (environment, git checkout, bundled providers). They share a name but operate at different layers and could compose: OpenShell templates define what to create, the K8s CRD defines how to schedule it. - #1268 — Inject read-only files into sandbox at creation time. Related workspace-setup concern (file injection before Landlock).
- #1706 — Emulate GCE metadata server for Google SDK access. Provider-level feature that templates would reference via bundled providers.
- #1423 — Make local dev credential discovery first-class. Provider-side complement — auto-discovered credentials compose with templates.
- #1492 — Add opaque driver-specific passthrough to SandboxTemplate. Actively extending the template proto.
Technical Context
Current state of the proto
SandboxTemplate already exists at proto/openshell.proto:335-363 with image, labels, annotations, environment, resources, and driver_config fields. But it is a flat struct embedded in SandboxSpec — not a named, reusable, gateway-persisted entity. SandboxSpec.environment also exists and flows through the gateway and compute drivers, but is not exposed by the CLI sandbox create command.
Current sandbox creation flow
CLI (sandbox create --provider X --from image)
→ Gateway: validates providers, resolves image, persists Sandbox, calls ComputeDriver
→ Driver (Docker/K8s/VM): builds env (defaults + template env + spec env + driver overrides), launches workload
→ Supervisor: loads policy, fetches provider credentials via GetSandboxProviderEnvironment RPC, spawns child process with provider env + proxy env + TLS env
Where env vars are set today
Four layers, in precedence order:
SandboxSpec.environment— proto field exists, flows correctly, CLI never populates itSandboxTemplate.environment— proto field exists, #863 reports it's broken- Driver-controlled env — identity, callback, security-critical vars (always override)
- Provider credential env — fetched at runtime by supervisor, injected as placeholders
Environment merge semantics with templates
When a named template is used, environment variables merge in this order (later layers win):
- Provider credential env — base layer from bundled (and any additionally attached) providers
- Template environment — merges on top of provider env, allowing templates to override or supplement provider-injected vars
- Inline
--envflags (future work) — would merge on top of template env for per-sandbox overrides
This means a template can set consumer-specific env vars (e.g., CLAUDE_CODE_USE_VERTEX=1) that sit on top of what the provider injects (e.g., GCP_PROJECT_ID), without modifying the provider itself.
Where git checkout happens today
Nowhere. The only related mechanism is --upload (tar-over-SSH of local files post-creation). No git clone during init.
Affected Components
| Component | Key Files | Role |
|---|---|---|
| Proto definitions | proto/openshell.proto |
Add NamedSandboxTemplate message, GitCheckout message, CRUD RPCs |
| Gateway server | crates/openshell-server/src/grpc/sandbox.rs |
Resolve template by name during sandbox creation |
| Gateway persistence | crates/openshell-server/src/ (store layer) |
Persist named templates |
| CLI | crates/openshell-cli/src/main.rs, crates/openshell-cli/src/run.rs |
template create/list/get/delete commands, sandbox create --template flag |
| Compute drivers | crates/openshell-driver-{docker,kubernetes,vm}/ |
Pass template env through to sandbox, handle git checkout volume |
| Python SDK | python/openshell/ |
Template CRUD bindings |
Technical Investigation
Architecture Overview
Named sandbox templates would be a new gateway-scoped domain object, similar to how providers are managed today:
NamedSandboxTemplate (gateway-persisted)
├── metadata (name, labels)
├── environment: map<string, string> — env vars merged on top of provider env
├── git_checkout: GitCheckout — repo to clone on init
├── providers: repeated string — default providers to attach
├── image: string — override default sandbox image
└── upload: repeated FileUpload — files to upload on init (composable with --upload)
message GitCheckout {
string url = 1; // Git repo URL
string ref = 2; // Branch, tag, or commit (default: HEAD)
string path = 3; // Clone target path inside sandbox (default: /home/user/<repo-name>)
bool shallow = 4; // --depth 1 for faster clones
}
Templates are a convenience, not a requirement. Users who don't need reusability continue using inline flags exactly as they do today. A template simply pre-populates what would otherwise be specified on every sandbox create invocation.
Git checkout: gateway-side clone (outside the sandbox)
Recommended: the gateway or compute driver clones the repo using credentials from a bundled provider (e.g., a github provider's token), then mounts/copies the checkout into the sandbox filesystem. Benefits:
- No git credentials inside the sandbox — the sandbox never sees the git token. The agent can read/edit code but cannot
git push. - Simpler lifecycle — no dependency on proxy being up, no network namespace concerns.
- Writable checkout — the agent can modify files; the
.gitdirectory could optionally be stripped or made read-only. - Composes with
--upload— both the template's git checkout and the user's--uploadpath work together (upload overlays on top of checkout).
Trade-off: the sandbox cannot git pull or git push without separate git access. Agent output would be extracted via sandbox exec, file download, or diff export.
Code References
| Location | Description |
|---|---|
proto/openshell.proto:309-332 |
SandboxSpec: has environment, template, providers fields |
proto/openshell.proto:335-363 |
SandboxTemplate: flat struct with environment, image, resources |
proto/openshell.proto:1089-1103 |
GetSandboxProviderEnvironmentResponse: provider env resolution |
crates/openshell-server/src/grpc/sandbox.rs:117-224 |
handle_create_sandbox_inner(): validates providers, resolves template |
crates/openshell-server/src/grpc/provider.rs:430-539 |
resolve_provider_environment(): provider credentials → env var map |
crates/openshell-cli/src/main.rs:1162-1293 |
CLI sandbox create command — no --env or --template flags |
crates/openshell-cli/src/run.rs:1740-1920 |
sandbox_create(): CLI-side creation logic |
crates/openshell-driver-docker/src/lib.rs:1634-1710 |
Docker driver build_environment() |
crates/openshell-sandbox/src/lib.rs:370-425 |
Supervisor fetches provider env at startup |
crates/openshell-sandbox/src/process.rs:194-258 |
inject_provider_env() + child process spawn |
Current Behavior
When sandbox create is called:
- CLI parses
--provider,--from,--policyflags — no--envor--templatesupport - Gateway validates providers exist, checks for credential key collisions, resolves image
SandboxSpecis persisted with emptyenvironmentmap (CLI never populates it)- Compute driver builds container env from defaults + template env + spec env + driver overrides
- Supervisor fetches provider credentials at runtime and injects into child process
What Would Need to Change
Proto layer:
- New
NamedSandboxTemplatemessage with metadata, environment, git_checkout, providers, image - New
GitCheckoutmessage - CRUD RPCs:
CreateTemplate,GetTemplate,ListTemplates,DeleteTemplate CreateSandboxRequestgains atemplate_namefield (or reuse existingtemplatefield)
Gateway:
- Template persistence (same store pattern as providers)
- Template resolution during sandbox creation: merge template fields into SandboxSpec
- Git checkout execution: clone repo using bundled provider credentials, make available to compute driver
CLI:
openshell template create/list/get/deletecommandsopenshell sandbox create --template <name>flag- Template overrides at creation time deferred to future work
Compute drivers:
- Accept git checkout directory/volume and mount into sandbox filesystem
- Docker: bind mount or copy into container
- K8s: init container or volume mount
- VM: include in rootfs or mount
Python SDK:
- Template CRUD methods on the client
Alternative Approaches Considered
Option A: Extend SandboxSpec inline (no named templates). Add --env and --git-repo flags to sandbox create. Simpler, but no reusability — you retype everything each time. Doesn't satisfy the "5 parallel sandboxes from the same config" use case well.
Option B: CLI-side config files only. Store templates as local YAML files, expand them client-side before sending to the gateway. Simpler (no new RPCs/persistence), but not shareable across machines or team members. #1520 explores this direction — the two could converge.
Option C: Named gateway-scoped templates (recommended). Full CRUD on the gateway. Reusable, shareable, composable with providers. More implementation work but the right long-term abstraction.
Patterns to Follow
- Provider CRUD in
crates/openshell-server/src/grpc/provider.rs— same pattern for template CRUD - Provider persistence in the gateway store — same pattern for template persistence
- Provider CLI commands — same pattern for template CLI commands
--uploadtar-over-SSH incrates/openshell-cli/src/run.rs:5669-5706— git checkout should compose with this
Proposed Approach
Introduce NamedSandboxTemplate as a gateway-persisted domain object with environment, git checkout, bundled providers, and optional image override. Templates are managed via CLI CRUD commands and referenced by name at sandbox creation time. Git checkout happens at the gateway/driver level before the sandbox starts, so git credentials never enter the sandbox. The --upload path remains supported and composes with template-driven checkout. Template environment merges on top of provider-injected env vars, giving templates a natural place for consumer-specific config that doesn't belong in the provider itself. Template overrides at sandbox creation time are deferred to future work.
Scope Assessment
- Complexity: High — new domain object, CRUD RPCs, persistence, CLI surface, git checkout orchestration across three compute drivers
- Confidence: Medium — core design is clear, but git checkout mechanics vary significantly across Docker/K8s/VM drivers
- Estimated files to change: 15-25
- Issue type:
feat
Risks & Open Questions
- #863 must be fixed first —
SandboxTemplate.environmentis currently broken. Template env vars flowing through is a prerequisite. - Git checkout across drivers — Docker bind mounts, K8s init containers/volumes, and VM rootfs injection are three different mechanisms. Need to define the driver interface for "make this directory available in the sandbox."
- Git credential scoping — if the template bundles a
githubprovider, the gateway can use that provider's token for the clone. But the clone happens outside the sandbox, so the gateway needs access to provider credentials at clone time, not just at sandbox runtime. - Template + provider interaction — when a template bundles providers, does the user need to have those providers already created? Or can the template reference provider types and auto-create instances?
- Upload + git checkout ordering — if both are specified, upload should overlay on top of the git checkout. Need to define sequencing.
- Template mutability — can a template be updated after creation? Do running sandboxes reflect updates, or are they snapshots?
- Relationship to #1520 — the YAML file format from #1520 could become the authoring surface for templates. Need alignment on whether templates are created via
openshell template create --from-file template.yamloropenshell apply -f template.yaml.
Test Considerations
- Unit tests: Template CRUD operations, template resolution during sandbox creation, environment merging (provider env → template env layering)
- Unit tests: Template + provider bundling — verify provider attachment, credential key collision checks
- Integration tests: Template persistence round-trip (create, get, list, delete)
- E2e tests: Create sandbox from template with env vars — verify env vars appear in sandbox and override provider env where specified
- E2e tests: Create sandbox from template with git checkout — verify repo is cloned and writable
- E2e tests: Create sandbox from template with bundled providers — verify providers are attached and credentials flow
- E2e tests: Template +
--uploadcomposition — verify both git checkout and uploaded files are present - E2e tests: Create sandbox without a template — verify existing inline workflow is unaffected
- Negative tests: Template with nonexistent provider, template with invalid git URL, duplicate template names
Created by spike investigation. Use build-from-issue to plan and implement.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with proto/openshell.proto, the provider CRUD pattern in crates/openshell-server/src/grpc/provider.rs, and handle_create_sandbox_inner() in grpc/sandbox.rs. Then trace CLI creation in crates/openshell-cli/src/main.rs and run.rs through the Docker, Kubernetes, and VM drivers. Done means named templates can be persisted and managed, selected during sandbox creation, and carry the proposed environment, providers, image, uploads, and git checkout across supported components.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, rust
- Domain
- api, backend, cli, infrastructure
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100