-
Notifications
You must be signed in to change notification settings - Fork 465
feat(cli): add openshell sandbox exec subcommand #750
Description
Problem Statement
There is no CLI command to run a one-off command inside an already-running sandbox. Today the workarounds are:
openshell sandbox create -- <cmd>- creates a new sandbox just to run a command.openshell sandbox connect- opens an interactive shell; there's no way to pass a single command and get stdout/stderr/exit-code back non-interactively.
This is the equivalent of docker exec or kubectl exec - a fundamental primitive for scripting, CI pipelines, and agent tool-use against running sandboxes.
Providing such a command will make it easier to create wrapper scripts that do additional configuration of sandboxes.
Proposed Design
Add a new Exec variant to SandboxCommands:
openshell sandbox exec <NAME> -- <COMMAND...>
Flags
| Flag | Description |
|---|---|
--tty / --no-tty |
Force/disable PTY allocation (auto-detect by default) |
--workdir <PATH> |
Set the working directory inside the sandbox |
--env KEY=VALUE |
Set environment variables (repeatable) |
--timeout <SECONDS> |
Kill the command after N seconds (0 = no timeout) |
Behavior
- Resolve sandbox by name (reuse existing
resolve_sandboxlogic). - Call the existing
ExecSandboxgRPC RPC (proto/openshell.proto:37-38) which already acceptssandbox_id,command,workdir,environment,timeout_seconds, andstdin. - Stream
ExecSandboxEventresponses to stdout/stderr. - Exit with the remote command's exit code.
Implementation sketch
The server-side implementation already exists in crates/openshell-server/src/grpc.rs:1011-1069. The client-side SSH plumbing exists in crates/openshell-cli/src/ssh.rs (sandbox_exec, sandbox_exec_without_exec). The new subcommand mainly needs to:
- Add an
Execvariant toSandboxCommandsincrates/openshell-cli/src/main.rs - Add a match arm in
run.rsthat resolves the sandbox and calls either the gRPCExecSandboxRPC or the existing SSH-basedsandbox_exec_without_exec()(the latter avoids replacing the CLI process) - Wire up
--tty,--workdir,--env,--timeoutflags
Example usage
# Run a command and capture output
openshell sandbox exec my-sandbox -- ls -la /workspace
# Pipe data through
echo "hello" | openshell sandbox exec my-sandbox -- cat
# Run with env vars and timeout
openshell sandbox exec my-sandbox --env FOO=bar --timeout 30 -- python train.py
# Non-interactive scripting
EXIT_CODE=$(openshell sandbox exec my-sandbox -- test -f /workspace/model.pt; echo $?)Alternatives Considered
-
Extend
sandbox connectwith-- <cmd>trailing args - Semantically wrong;connectimplies an interactive session. Docker separatesattach(interactive) fromexec(run command) for good reason. -
Expose the gRPC API only and let users call it via
grpcurl- Poor DX. The CLI should wrap this, just as it wraps every other gRPC call. -
Use
sandbox connect+ssh -t <host> <cmd>manually - Requires users to manage SSH config themselves. The point of the CLI is to abstract this away.
Agent Investigation
- The
ExecSandboxgRPC RPC is already defined (proto/openshell.proto:37-38) and implemented server-side (crates/openshell-server/src/grpc.rs:1011-1069). - The SSH execution helpers
sandbox_exec()andsandbox_exec_without_exec()exist incrates/openshell-cli/src/ssh.rs:430. sandbox createusessandbox_exec()after the sandbox reaches Ready phase (run.rs:2405), proving the exec path works end-to-end.- The
SandboxCommandsenum (main.rs:1070) has noExecvariant - this is the only missing piece. openshell doctor execexists but targets the gateway container, not sandboxes.