Skip to content

feat(cli): add sandbox exec subcommand with TTY support#752

Merged
drew merged 2 commits intomainfrom
feat/sandbox-exec-cli
Apr 5, 2026
Merged

feat(cli): add sandbox exec subcommand with TTY support#752
drew merged 2 commits intomainfrom
feat/sandbox-exec-cli

Conversation

@drew
Copy link
Copy Markdown
Collaborator

@drew drew commented Apr 4, 2026

Summary

Add openshell sandbox exec subcommand that runs a command inside an already-running sandbox via the gRPC ExecSandbox streaming RPC, with full --tty/--no-tty support and hardened stdin handling.

Related Issue

Closes #750

Changes

  • proto/openshell.proto: Add bool tty = 7 field to ExecSandboxRequest for PTY allocation
  • crates/openshell-cli/src/main.rs: Add Exec variant to SandboxCommands with --name, --workdir, --env, --timeout, --tty/--no-tty flags; add dispatch in main()
  • crates/openshell-cli/src/run.rs: Implement sandbox_exec_grpc() — resolves sandbox, validates phase is Ready, parses env pairs, reads stdin via spawn_blocking with 4 MiB size limit, streams gRPC output to stdout/stderr, returns remote exit code
  • crates/openshell-server/src/grpc.rs: Plumb tty field through stream_exec_over_sshrun_exec_with_russh; call channel.request_pty() before channel.exec() when TTY is requested

Design decisions

  • --name as flag not positional: The issue spec shows <NAME> as positional, but combining a positional name with trailing_var_arg for command creates parsing ambiguity when relying on the last-used sandbox fallback. The --name/-n flag resolves this cleanly.
  • Client-side phase check: The server already checks SandboxPhase::Ready, but the client now provides a user-friendly error message instead of a raw gRPC FAILED_PRECONDITION.
  • save_last_sandbox after exec: Other subcommands (e.g., Connect) save after the operation succeeds. Moved to match that pattern.
  • 4 MiB stdin limit: Prevents the CLI from reading unbounded piped input into memory before the server rejects it.

Testing

  • mise run pre-commit passes (license check failure is pre-existing local tmp/ file, unrelated)
  • All 743 unit tests pass
  • E2E tests added/updated (not applicable — no changes under e2e/)

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (if applicable)

@drew drew requested a review from a team as a code owner April 4, 2026 02:01
@drew drew added the area:cli CLI-related work label Apr 4, 2026
@drew drew self-assigned this Apr 4, 2026
johntmyers
johntmyers previously approved these changes Apr 4, 2026
Copy link
Copy Markdown
Collaborator

@johntmyers johntmyers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One nit on unbounded read and generally curious if we run any risks on this replacing how long running processes should run esp wrt env var injection

Add a new 'openshell sandbox exec' command that executes commands inside
running sandboxes using the gRPC ExecSandbox streaming endpoint (the same
endpoint the Python SDK uses). This provides a lightweight alternative to
SSH-based execution for non-interactive command runs.

Key features:
- Real-time stdout/stderr streaming to the terminal
- Exit code propagation (CLI exits with the remote command's exit code)
- Piped stdin support (automatically detected when stdin is not a TTY)
- Environment variable overrides via --env/-e flags
- Working directory override via --workdir
- Configurable timeout (exit code 124 on timeout, matching Unix convention)
- Last-used sandbox fallback when --name is omitted
@drew drew force-pushed the feat/sandbox-exec-cli branch 2 times, most recently from fcc2672 to a64e433 Compare April 5, 2026 03:52
Add missing --tty/--no-tty flag required by the issue spec, plumbed
through proto, CLI, and server-side PTY allocation over SSH.

Harden the implementation with a client-side sandbox phase check,
a 4 MiB stdin size limit to prevent OOM, and spawn_blocking for
stdin reads to avoid blocking the async runtime. Move
save_last_sandbox after exec succeeds for consistency.

Closes #750
@drew drew force-pushed the feat/sandbox-exec-cli branch from a64e433 to ab266c1 Compare April 5, 2026 03:54
@drew
Copy link
Copy Markdown
Collaborator Author

drew commented Apr 5, 2026

run any risks on this replacing how long running processes should run

The exec command creates a new SSH channel and spawns a fresh process for each invocation — it doesn't attach to or signal any pre-existing processes in the sandbox.

esp wrt env var injection

I punted the env variable issue for now. We should solve this with providers down the road.

@drew drew merged commit 13262e1 into main Apr 5, 2026
9 checks passed
@drew drew deleted the feat/sandbox-exec-cli branch April 5, 2026 04:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:cli CLI-related work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(cli): add openshell sandbox exec subcommand

2 participants