Skip to content

fix(exec): treat remote non-zero exit as command failure, not connection failure - #473

Open
abhtripathi wants to merge 2 commits into
mainfrom
BREV-11814/exec-remote-exit-code
Open

fix(exec): treat remote non-zero exit as command failure, not connection failure#473
abhtripathi wants to merge 2 commits into
mainfrom
BREV-11814/exec-remote-exit-code

Conversation

@abhtripathi

Copy link
Copy Markdown
Contributor

Problem

brev exec checked only whether the ssh process errored, not its exit code. ssh reserves 255 for its own failures and passes every other code through from the remote command. We treated both the same, so any command exiting non-zero was read as a dead connection and triggered the full recovery path: workspace lookup, org-wide refresh, cloudflared check, SSH probe, and a duplicate re-run of the command.

This is what was causing customer app timeouts when scripting brev exec in a loop.

Changes

  • classifySSHError branches on the exit code: 255 stays a connection failure, anything else becomes a typed RemoteExitError
  • Run ssh via exec in the bash wrapper so the observed code is ssh's own, not bash's
  • Fast path returns RemoteExitError immediately, skipping the recovery path entirely
  • Extracted runAndTrack so both recovery branches handle it explicitly and no longer skip analytics on non-zero exits
  • flattenMultiInstanceErr so a multi-instance run exits 1 instead of picking an arbitrary instance's code out of the multierror
  • main.go propagates the real remote exit code instead of always exiting 1, and skips the Sentry report since a non-zero command isn't a CLI error
  • One-line notice when there are no saved credentials, using a local-only token read (no network, no prompt). Behavior is otherwise unchanged: exec still runs, and the login prompt still appears where it does today.

Testing

New pkg/cmd/exec/exec_test.go, 5 tests, all passing. Mutation-checked: reverting the 255 logic makes them fail.

TestRemoteExitErrorSurvivesWrapping covers the recovery path specifically, confirming errors.As matches through WrapAndTrace, double-wrap, and multierror.

Manually verified against a live instance: exit codes 0/1/7/127 propagate correctly, multi-instance exits 1, and a genuinely unreachable host still gets the full recovery path (ssh returns 255).

@abhtripathi
abhtripathi requested a review from a team as a code owner September 8, 2026 09:24
@abhtripathi
abhtripathi force-pushed the BREV-11814/exec-remote-exit-code branch from bd4e8a7 to e130310 Compare September 8, 2026 09:39
Comment thread main.go Outdated
Comment on lines +21 to +27
// A remote command exiting non-zero is not a CLI error: pass its exit
// code through so callers can branch on it, and print nothing extra.
var remoteErr exec.RemoteExitError
if stderrors.As(err, &remoteErr) {
done()
os.Exit(remoteErr.Code) //nolint:gocritic // manually call done
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there a way to isolate the changes only to the exec command itself? I was surprised to see that we needed to change the root command handler.

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.

2 participants