From acbfaeadfa3303e785d7aeb353b319b8da6a63d6 Mon Sep 17 00:00:00 2001 From: Tynan Daly Date: Mon, 8 Dec 2025 14:25:26 -0500 Subject: [PATCH 1/2] restore the terminal when exiting raw mdoe --- internal/handlers/connect.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/internal/handlers/connect.go b/internal/handlers/connect.go index 97038ed..4649014 100644 --- a/internal/handlers/connect.go +++ b/internal/handlers/connect.go @@ -3,6 +3,7 @@ package handlers import ( "context" "fmt" + "os" "os/exec" "strings" @@ -12,6 +13,7 @@ import ( vmSvc "github.com/hdresearch/vers-cli/internal/services/vm" sshutil "github.com/hdresearch/vers-cli/internal/ssh" "github.com/hdresearch/vers-cli/internal/utils" + "golang.org/x/term" ) type ConnectReq struct{ Target string } @@ -49,8 +51,19 @@ func HandleConnect(ctx context.Context, a *app.App, r ConnectReq) (presenters.Co // Render connection info BEFORE running SSH so it displays before connecting presenters.RenderConnect(a, view) + // Save terminal state before SSH so we can restore it if the connection + // exits abnormally (network drop, server crash, etc.) + fd := int(os.Stdin.Fd()) + oldState, termErr := term.GetState(fd) + args := sshutil.SSHArgs(sshHost, sshPort, info.KeyPath) err = a.Runner.Run(ctx, "ssh", args, runrt.Stdio{In: a.IO.In, Out: a.IO.Out, Err: a.IO.Err}) + + // Always restore terminal state after SSH exits, regardless of how it exited + if termErr == nil && oldState != nil { + _ = term.Restore(fd, oldState) + } + if err != nil { if _, ok := err.(*exec.ExitError); !ok { return view, fmt.Errorf("failed to run SSH command: %w", err) From 25b56c4883b265da06da93a48eabab0bb5a1410b Mon Sep 17 00:00:00 2001 From: Tynan Daly Date: Mon, 8 Dec 2025 14:33:44 -0500 Subject: [PATCH 2/2] use proper context for interactive ssh session (no timeout) --- cmd/connect.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/connect.go b/cmd/connect.go index 668204c..65f672f 100644 --- a/cmd/connect.go +++ b/cmd/connect.go @@ -14,13 +14,14 @@ var connectCmd = &cobra.Command{ Long: `Connect to a running Vers VM via SSH. If no VM ID or alias is provided, uses the current HEAD.`, Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - apiCtx, cancel := context.WithTimeout(context.Background(), application.Timeouts.APIMedium) - defer cancel() + // Use a context without timeout for interactive SSH sessions. + // The SSH connection should stay open until the user exits. + ctx := context.Background() target := "" if len(args) > 0 { target = args[0] } - _, err := handlers.HandleConnect(apiCtx, application, handlers.ConnectReq{Target: target}) + _, err := handlers.HandleConnect(ctx, application, handlers.ConnectReq{Target: target}) if err != nil { return err }