Skip to content

feat(cli): add openshell sandbox exec subcommand #750

@steve-todorov

Description

@steve-todorov

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

  1. Resolve sandbox by name (reuse existing resolve_sandbox logic).
  2. Call the existing ExecSandbox gRPC RPC (proto/openshell.proto:37-38) which already accepts sandbox_id, command, workdir, environment, timeout_seconds, and stdin.
  3. Stream ExecSandboxEvent responses to stdout/stderr.
  4. 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 Exec variant to SandboxCommands in crates/openshell-cli/src/main.rs
  • Add a match arm in run.rs that resolves the sandbox and calls either the gRPC ExecSandbox RPC or the existing SSH-based sandbox_exec_without_exec() (the latter avoids replacing the CLI process)
  • Wire up --tty, --workdir, --env, --timeout flags

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

  1. Extend sandbox connect with -- <cmd> trailing args - Semantically wrong; connect implies an interactive session. Docker separates attach (interactive) from exec (run command) for good reason.

  2. 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.

  3. 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 ExecSandbox gRPC 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() and sandbox_exec_without_exec() exist in crates/openshell-cli/src/ssh.rs:430.
  • sandbox create uses sandbox_exec() after the sandbox reaches Ready phase (run.rs:2405), proving the exec path works end-to-end.
  • The SandboxCommands enum (main.rs:1070) has no Exec variant - this is the only missing piece.
  • openshell doctor exec exists but targets the gateway container, not sandboxes.

Metadata

Metadata

Assignees

Labels

area:cliCLI-related work

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions