@@ -176,61 +176,110 @@ func parseCommand(command string) (string, error) {
176176const pollTimeout = 10 * time .Minute
177177
178178func runExecCommand (t * terminal.Terminal , sstore ExecStore , workspaceNameOrID string , host bool , command string ) error {
179- s := t .NewSpinner ()
180- workspace , err := util .GetUserWorkspaceByNameOrIDErr (sstore , workspaceNameOrID )
181- if err != nil {
182- return breverrors .WrapAndTrace (err )
179+ // Determine SSH alias: use the workspace name directly (with -host suffix if needed)
180+ sshName := workspaceNameOrID
181+ if host {
182+ sshName = workspaceNameOrID + "-host"
183+ }
184+
185+ // Fire SSH immediately with a short timeout — skip all status checks for speed.
186+ // SSH multiplexing (ControlMaster) in the config means subsequent
187+ // calls reuse an existing connection and are near-instant.
188+ // Use a 5-second connect timeout so we fail fast if the instance is down.
189+ err := runSSHWithTimeout (sshName , command , 5 )
190+ if err == nil {
191+ // Success — fire analytics in background and return
192+ go trackExecAnalytics (sstore , workspaceNameOrID )
193+ return nil
183194 }
184195
185- if workspace .Status == "STOPPED" { // we start the env for the user
186- err = util .StartWorkspaceIfStopped (t , s , sstore , workspaceNameOrID , workspace , pollTimeout )
196+ // SSH failed — now check what's going on with the instance
197+ fmt .Fprintf (os .Stderr , "Connection failed, checking instance status...\n " )
198+
199+ workspace , lookupErr := util .GetUserWorkspaceByNameOrIDErr (sstore , workspaceNameOrID )
200+ if lookupErr != nil {
201+ return breverrors .WrapAndTrace (fmt .Errorf (
202+ "ssh connection failed and could not look up instance %q: %w\n Please check your instances with: brev ls" ,
203+ workspaceNameOrID , err ))
204+ }
205+
206+ if workspace .Status == "STOPPED" {
207+ s := t .NewSpinner ()
208+ startErr := util .StartWorkspaceIfStopped (t , s , sstore , workspaceNameOrID , workspace , pollTimeout )
209+ if startErr != nil {
210+ return breverrors .WrapAndTrace (startErr )
211+ }
212+ err = util .PollUntil (s , workspace .ID , "RUNNING" , sstore , " waiting for instance to be ready..." , pollTimeout )
213+ if err != nil {
214+ return breverrors .WrapAndTrace (err )
215+ }
216+ // Refresh SSH config so the host entry is up to date
217+ refreshRes := refresh .RunRefreshAsync (sstore )
218+ if err = refreshRes .Await (); err != nil {
219+ return breverrors .WrapAndTrace (err )
220+ }
221+
222+ localIdentifier := workspace .GetLocalIdentifier ()
223+ if host {
224+ localIdentifier = workspace .GetHostIdentifier ()
225+ }
226+ sshName = string (localIdentifier )
227+
228+ err = util .WaitForSSHToBeAvailable (sshName , s )
187229 if err != nil {
188230 return breverrors .WrapAndTrace (err )
189231 }
232+ _ = writeconnectionevent .WriteWCEOnEnv (sstore , workspace .DNS )
233+ err = runSSH (sshName , command )
234+ if err != nil {
235+ return breverrors .WrapAndTrace (err )
236+ }
237+ go trackExecAnalytics (sstore , workspaceNameOrID )
238+ return nil
190239 }
191- err = util .PollUntil (s , workspace .ID , "RUNNING" , sstore , " waiting for instance to be ready..." , pollTimeout )
192- if err != nil {
193- return breverrors .WrapAndTrace (err )
240+
241+ if workspace .Status != "RUNNING" {
242+ return breverrors .WrapAndTrace (fmt .Errorf (
243+ "instance %q is in state %q — please check with: brev ls" ,
244+ workspaceNameOrID , workspace .Status ))
194245 }
195- refreshRes := refresh .RunRefreshAsync (sstore )
196246
197- workspace , err = util .GetUserWorkspaceByNameOrIDErr (sstore , workspaceNameOrID )
198- if err != nil {
247+ // Instance is RUNNING but SSH failed — maybe still booting, do the wait
248+ s := t .NewSpinner ()
249+ refreshRes := refresh .RunRefreshAsync (sstore )
250+ if err = refreshRes .Await (); err != nil {
199251 return breverrors .WrapAndTrace (err )
200252 }
201- if workspace .Status != "RUNNING" {
202- return breverrors .New ("Instance is not running" )
203- }
204253
205254 localIdentifier := workspace .GetLocalIdentifier ()
206255 if host {
207256 localIdentifier = workspace .GetHostIdentifier ()
208257 }
258+ sshName = string (localIdentifier )
209259
210- sshName := string (localIdentifier )
211-
212- err = refreshRes .Await ()
213- if err != nil {
214- return breverrors .WrapAndTrace (err )
215- }
216260 err = util .WaitForSSHToBeAvailable (sshName , s )
217261 if err != nil {
218- return breverrors .WrapAndTrace (err )
262+ return breverrors .WrapAndTrace (fmt .Errorf (
263+ "could not connect to instance %q: %w\n Please check with: brev ls" ,
264+ workspaceNameOrID , err ))
219265 }
220- // we don't care about the error here but should log with sentry
221- // legacy environments wont support this and cause errrors,
222- // but we don't want to block the user from using the shell
223266 _ = writeconnectionevent .WriteWCEOnEnv (sstore , workspace .DNS )
224267 err = runSSH (sshName , command )
225268 if err != nil {
226269 return breverrors .WrapAndTrace (err )
227270 }
228- // Call analytics for exec
229- userID := ""
230- user , err := sstore .GetCurrentUser ()
271+ go trackExecAnalytics (sstore , workspaceNameOrID )
272+ return nil
273+ }
274+
275+ func trackExecAnalytics (sstore ExecStore , workspaceNameOrID string ) {
276+ workspace , err := util .GetUserWorkspaceByNameOrIDErr (sstore , workspaceNameOrID )
231277 if err != nil {
232- userID = workspace .CreatedByUserID
233- } else {
278+ return
279+ }
280+ userID := workspace .CreatedByUserID
281+ user , err := sstore .GetCurrentUser ()
282+ if err == nil {
234283 userID = user .ID
235284 }
236285 data := analytics.EventData {
@@ -241,19 +290,17 @@ func runExecCommand(t *terminal.Terminal, sstore ExecStore, workspaceNameOrID st
241290 },
242291 }
243292 _ = analytics .TrackEvent (data )
244-
245- return nil
246293}
247294
248- func runSSH (sshAlias string , command string ) error {
249- sshAgentEval := "eval $(ssh-agent -s)"
250-
295+ func runSSHWithTimeout (sshAlias string , command string , connectTimeoutSecs int ) error {
251296 // Non-interactive: run command and pipe stdout/stderr
252297 // Escape the command for passing to SSH
253298 escapedCmd := strings .ReplaceAll (command , "'" , "'\\ ''" )
254- cmd := fmt .Sprintf ("ssh %s '%s'" , sshAlias , escapedCmd )
299+ // -T disables pseudo-terminal allocation (no "Pseudo-terminal will not be allocated" warning)
300+ // Only start ssh-agent if one isn't already running (avoids orphaned agent processes)
301+ agentCmd := `if [ -z "$SSH_AUTH_SOCK" ]; then eval $(ssh-agent -s) > /dev/null; fi`
302+ cmd := fmt .Sprintf ("%s && ssh -T -o ConnectTimeout=%d -o LogLevel=ERROR %s '%s'" , agentCmd , connectTimeoutSecs , sshAlias , escapedCmd )
255303
256- cmd = fmt .Sprintf ("%s && %s" , sshAgentEval , cmd )
257304 sshCmd := exec .Command ("bash" , "-c" , cmd ) //nolint:gosec //cmd is user input
258305 sshCmd .Stderr = os .Stderr
259306 sshCmd .Stdout = os .Stdout
@@ -265,3 +312,7 @@ func runSSH(sshAlias string, command string) error {
265312 }
266313 return nil
267314}
315+
316+ func runSSH (sshAlias string , command string ) error {
317+ return runSSHWithTimeout (sshAlias , command , 10 )
318+ }
0 commit comments