fix(dispatch): stop PTY teardown from killing one-click CLI installs - #2153
Merged
Merged
Conversation
The SSH CLI installer launched its driver over a PTY exec channel. The driver only spawns a nohup body and exits, which it does about a millisecond later, and sshd tears the PTY down the moment it does. That teardown races the body: a body still inside bash's startup has not reached its own exit trap yet, so losing the race kills it silently. Nothing survives to explain it. The body writes no log and no exit file, the driver's cleanup trap removes the `.preparing` marker, and the next poll reaps the now-stale `.pid`. The controller reads an empty state, maps it to Failed on its very first poll, and the user gets "could not fully deploy BitFun" for an install that had already downloaded and verified the release. Launch over a plain exec channel instead. Without a controlling terminal there is no hangup to race, and the installer needs no TTY semantics anyway since it never uses sudo. Two diagnostics gaps kept this invisible and are fixed alongside it: - WebKit, which Tauri embeds on macOS, builds `Error.stack` from frames only. The logger preferred `stack` over the message, so the warning reached the log file as a bare source location with no reason. - The install poll's failure discarded the installer's own output, so even a populated remote log never reached the error. Verified against a real Ubuntu aarch64 target. Launching a detached process from a PTY channel is killed before it writes a line; over a plain channel it survives and the install completes (running=1 on the first poll, then marker=1 exit_code=0).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
One-click deploy to an SSH target failed with "未能完整部署 BitFun" even though the release had already been downloaded and verified on the target.
The installer launched its driver over a PTY exec channel. The driver only spawns a
nohupbody and exits — about a millisecond later — and sshd tears the PTY down the moment it does. That teardown races the body it just spawned: a body still inside bash's startup has not reached its owntrap finish EXITyet, so losing the race kills it silently.Nothing survives to explain it:
.exitfile,.preparingmarker,.pidvia its liveness check.install_cli_pollreads an all-empty state, maps it toFailed, and the dialog treats the very first poll as fatal.Fix: launch over a plain exec channel. Without a controlling terminal there is no hangup to race, and the installer needs no TTY semantics anyway — it never uses sudo.
Type and Areas
Type: bug fix (+ two diagnostics fixes that kept it invisible)
Areas: Rust core (
remote_ssh/dispatch), web UIMotivation / Impact
The failure is probabilistic, which is why the same target sometimes installs fine and sometimes does not. When it loses the race the user gets a generic, unactionable error and a target left with a verified archive and no CLI.
Two diagnostics gaps are fixed alongside it, because together they made the root cause unrecoverable from the logs:
logger.ts— WebKit (what Tauri embeds on macOS) buildsError.stackfrom frames only, with no message line. The logger preferredstackover the message, so the warning reachedapp.logasFailed to prepare SSH dispatch target {...}, @tauri://localhost/assets/ChatPane-*.js:96:14836— a location with no reason. This affects every call site that logs anError.DispatchInstallDialog.tsx— the install-poll failure threw a fixed string and discardedpoll.output, so even a populated remote installer log never reached the error.No user-facing string changes.
Verification
Diagnosed and verified against a real Ubuntu 24.04 aarch64 target over SSH, using a standalone russh 0.45 program that replicates
stage_and_launch_installerexactly (PTY, background drain, first poll at +52 ms — the timing taken from the failingapp.log).Detachment matrix, launching a
nohup'd background script and letting the channel close:nohup+&setsid+nohup+&nohup+&Measured: the driver runs start→exit in ~1 ms, and a hangup reaches the remote process in ~3 ms — comfortably inside the window before the body installs its traps.
End-to-end with the real staged installer over a non-PTY channel, polling on the app's schedule:
Target ends on
bitfun 0.2.16, install directory clean, archive consumed as designed.Automated checks:
Two regression tests added for the logger's error formatting (stack-without-message, and no duplication when the stack already carries it).
Reviewer Notes
provision_account_daemon) runs throughexecute_command_with_optionsand has no PTY, so it never had this hazard.open_exec_channelpassestty: falsetoworkspace_command, which only matters for Docker-container targets — andensure_plain_ssh_targetalready rejects those before this point, so the change is a no-op for every path that reaches it.