[DEP-3694] feat(compute): add exec and pty commands#444
[DEP-3694] feat(compute): add exec and pty commands#444nagypeterjob wants to merge 1 commit intomainfrom
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Bugbot Autofix prepared fixes for all 3 issues found in the latest run.
- ✅ Fixed: Stderr output incorrectly written to stdout
- The stderr stream handler now writes
ExecuteCommandResponse_Stderroutput toos.Stderrinstead ofos.Stdout.
- The stderr stream handler now writes
- ✅ Fixed: os.Exit skips deferred terminal restore leaving raw mode
- Before calling
os.Exiton non-zero PTY exit codes, the code now explicitly restores the terminal state to avoid leaving raw mode active.
- Before calling
- ✅ Fixed: Concurrent stream.Send calls cause data race
- All
stream.Sendcalls in PTY mode are now serialized through a mutex-protected send helper to prevent concurrent sends.
- All
Or push these changes by commenting:
@cursor push 2d77146e14
Preview (2d77146e14)
diff --git a/pkg/cmd/compute/exec.go b/pkg/cmd/compute/exec.go
--- a/pkg/cmd/compute/exec.go
+++ b/pkg/cmd/compute/exec.go
@@ -75,7 +75,7 @@
case *civ1.ExecuteCommandResponse_Stdout:
fmt.Fprint(os.Stdout, v.Stdout)
case *civ1.ExecuteCommandResponse_Stderr:
- fmt.Fprint(os.Stdout, v.Stderr)
+ fmt.Fprint(os.Stderr, v.Stderr)
case *civ1.ExecuteCommandResponse_ExitCode:
if v.ExitCode != 0 {
os.Exit(int(v.ExitCode))
diff --git a/pkg/cmd/compute/pty.go b/pkg/cmd/compute/pty.go
--- a/pkg/cmd/compute/pty.go
+++ b/pkg/cmd/compute/pty.go
@@ -7,6 +7,7 @@
"os"
"os/signal"
"strings"
+ "sync"
"syscall"
"github.com/depot/cli/pkg/api"
@@ -88,9 +89,15 @@
client := api.NewComputeClient()
stream := client.OpenPtySession(ctx)
stream.RequestHeader().Set("Authorization", "Bearer "+token)
+ var sendMu sync.Mutex
+ send := func(req *civ1.OpenPtySessionRequest) error {
+ sendMu.Lock()
+ defer sendMu.Unlock()
+ return stream.Send(req)
+ }
// Send init message.
- if err := stream.Send(&civ1.OpenPtySessionRequest{
+ if err := send(&civ1.OpenPtySessionRequest{
Message: &civ1.OpenPtySessionRequest_Init{
Init: &civ1.PtySession{
SandboxId: sandboxID,
@@ -116,7 +123,7 @@
if err != nil {
continue
}
- _ = stream.Send(&civ1.OpenPtySessionRequest{
+ _ = send(&civ1.OpenPtySessionRequest{
Message: &civ1.OpenPtySessionRequest_WindowResize{
WindowResize: &civ1.WindowResize{
Rows: uint32(h),
@@ -133,7 +140,7 @@
for {
n, err := os.Stdin.Read(buf)
if n > 0 {
- _ = stream.Send(&civ1.OpenPtySessionRequest{
+ _ = send(&civ1.OpenPtySessionRequest{
Message: &civ1.OpenPtySessionRequest_Stdin{Stdin: buf[:n]},
})
}
@@ -159,6 +166,7 @@
case *civ1.OpenPtySessionResponse_ExitCode:
fmt.Fprintf(os.Stderr, "\r\n[exit %d]\r\n", m.ExitCode)
if m.ExitCode != 0 {
+ _ = term.Restore(fd, oldState)
os.Exit(int(m.ExitCode))
}
return nil| case *civ1.ExecuteCommandResponse_Stdout: | ||
| fmt.Fprint(os.Stdout, v.Stdout) | ||
| case *civ1.ExecuteCommandResponse_Stderr: | ||
| fmt.Fprint(os.Stdout, v.Stderr) |
There was a problem hiding this comment.
Stderr output incorrectly written to stdout
High Severity
The ExecuteCommandResponse_Stderr case writes to os.Stdout instead of os.Stderr. This mixes remote stderr output into the stdout stream, making it impossible for callers to distinguish between stdout and stderr when piping or redirecting output.
| case *civ1.OpenPtySessionResponse_ExitCode: | ||
| fmt.Fprintf(os.Stderr, "\r\n[exit %d]\r\n", m.ExitCode) | ||
| if m.ExitCode != 0 { | ||
| os.Exit(int(m.ExitCode)) |
There was a problem hiding this comment.
os.Exit skips deferred terminal restore leaving raw mode
High Severity
When the remote process exits with a non-zero exit code, os.Exit is called, which skips all deferred functions — including the critical term.Restore on line 86. This leaves the user's terminal stuck in raw mode, making it unusable (no echo, no line editing, garbled output) until they manually run reset.
Additional Locations (1)
| return | ||
| } | ||
| } | ||
| }() |
There was a problem hiding this comment.
Concurrent stream.Send calls cause data race
Medium Severity
Two goroutines — the SIGWINCH handler (line 119) and the stdin forwarder (line 136) — both call stream.Send concurrently without synchronization. BidiStreamForClient.Send is not safe for concurrent use, so this is a data race that can corrupt the gRPC stream.
| case *civ1.ExecuteCommandResponse_Stdout: | ||
| fmt.Fprint(os.Stdout, v.Stdout) | ||
| case *civ1.ExecuteCommandResponse_Stderr: | ||
| fmt.Fprint(os.Stdout, v.Stderr) |
There was a problem hiding this comment.
Stderr output is being written to stdout instead of stderr. This will cause stderr messages to appear in stdout, breaking any tools or scripts that parse command output separately.
// Should be:
fmt.Fprint(os.Stderr, v.Stderr)| fmt.Fprint(os.Stdout, v.Stderr) | |
| fmt.Fprint(os.Stderr, v.Stderr) |
Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.



Note
Medium Risk
Adds new interactive streaming RPC paths (PTY/raw terminal handling and process exit semantics) and upgrades several foundational networking/telemetry dependencies, which could affect runtime behavior and compatibility.
Overview
Adds a new top-level
depot computecommand withexec(server-streamed remote command execution with optional timeout) andpty(interactive pseudo-terminal session with env/cwd support, raw-mode stdin forwarding, and SIGWINCH resize handling).Introduces a new
DepotComputeServiceproto + generated Connect client/server stubs, and wires aNewComputeClient()inpkg/api/rpc.go(usingconnect.WithGRPC()) so the CLI can callRemoteExecandOpenPtySession.Updates Go dependencies substantially (notably
google.golang.org/grpc,google.golang.org/protobuf,golang.org/x/*, and OpenTelemetry), plus addsgo.opentelemetry.io/auto/sdk.Written by Cursor Bugbot for commit f5e700c. This will update automatically on new commits. Configure here.