Skip to content

[DEP-3694] feat(compute): add exec and pty commands#444

Open
nagypeterjob wants to merge 1 commit intomainfrom
DEP-3694-feat-add-compute-commands
Open

[DEP-3694] feat(compute): add exec and pty commands#444
nagypeterjob wants to merge 1 commit intomainfrom
DEP-3694-feat-add-compute-commands

Conversation

@nagypeterjob
Copy link

@nagypeterjob nagypeterjob commented Mar 10, 2026

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 compute command with exec (server-streamed remote command execution with optional timeout) and pty (interactive pseudo-terminal session with env/cwd support, raw-mode stdin forwarding, and SIGWINCH resize handling).

Introduces a new DepotComputeService proto + generated Connect client/server stubs, and wires a NewComputeClient() in pkg/api/rpc.go (using connect.WithGRPC()) so the CLI can call RemoteExec and OpenPtySession.

Updates Go dependencies substantially (notably google.golang.org/grpc, google.golang.org/protobuf, golang.org/x/*, and OpenTelemetry), plus adds go.opentelemetry.io/auto/sdk.

Written by Cursor Bugbot for commit f5e700c. This will update automatically on new commits. Configure here.

@linear
Copy link

linear bot commented Mar 10, 2026

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

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_Stderr output to os.Stderr instead of os.Stdout.
  • ✅ Fixed: os.Exit skips deferred terminal restore leaving raw mode
    • Before calling os.Exit on non-zero PTY exit codes, the code now explicitly restores the terminal state to avoid leaving raw mode active.
  • ✅ Fixed: Concurrent stream.Send calls cause data race
    • All stream.Send calls in PTY mode are now serialized through a mutex-protected send helper to prevent concurrent sends.

Create PR

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)
Copy link

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

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))
Copy link

Choose a reason for hiding this comment

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

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)

Fix in Cursor Fix in Web

return
}
}
}()
Copy link

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

case *civ1.ExecuteCommandResponse_Stdout:
fmt.Fprint(os.Stdout, v.Stdout)
case *civ1.ExecuteCommandResponse_Stderr:
fmt.Fprint(os.Stdout, v.Stderr)
Copy link
Contributor

Choose a reason for hiding this comment

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

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)
Suggested change
fmt.Fprint(os.Stdout, v.Stderr)
fmt.Fprint(os.Stderr, v.Stderr)

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant