Repository navigation
Conversation
watsonian
added this pull request to stack #566
October 13, 2026 15:34
`utils.RunCommand` registered a handler for every signal and never removed it. When an update was installed from the version-check prompt, every later Ctrl-C in the same process was swallowed, so `doppler login` could only end at its 5 minute timeout. The winget checks did the same on Windows. Helper commands no longer catch signals. The install script forwards SIGINT/SIGTERM only while it runs, so an interrupted update still reports the failure and removes its temp file. `doppler run` now owns signal handling for its lifetime and forwards to the current child. A termination signal during a --watch restart cancels the restart: doppler exits by that signal once no child is running instead of starting a new one. `--forward-signals` behaves as before. Adds end-to-end signal tests that run the CLI against a mock API.
watsonian
force-pushed
the
watsonian/fix-signal-handling
branch
from
October 13, 2026 16:37
e5b84c0 to
2025ae0
Compare
This branch has not been deployed
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.
A user reported that they couldn't cancel
doppler loginwith Ctrl-C while it was waiting for browser authorization. The command only ended when the five-minute timeout fired. Both times it happened, the CLI had just offered an update and the user had accepted it in the same invocation.The cause was
utils.RunCommand. Every time it started a subprocess, it registered a handler for all signals and never removed it. The update check runs before every command, and accepting the update runs the install script throughRunCommand. After that, every Ctrl-C was caught and forwarded to an installer that had already exited, so the rest of the command, heredoppler login, couldn't be interrupted. The winget checks on Windows also go throughRunCommand, and they run on every command when a check is due (at most once every 24 hours) whether or not an update is offered. So Windows users installed via winget could hit this without accepting anything.This PR removes signal handling from
RunCommandandRunCommandString. The install script is the one helper that still needs it: if the user presses Ctrl-C mid-install, we want to report the failure and delete the temporary script rather than die and leave a file in their home directory. A newutils.ForwardSignalsforwards SIGINT and SIGTERM to the script only while it runs.doppler runwas the only command that relied on the old behavior. It now owns its signal handling throughrunSignalHandlerinpkg/cmd/run_signals.go. The handler starts listening just before the first child starts, so Ctrl-C while secrets are still loading keeps its default behavior. From then on, it forwards signals to whichever child is currently running, which matters after a--watchrestart.--forward-signalsand its TTY-dependent default behave exactly as before.The one deliberate behavior change is for a termination signal that arrives while
--watchis restarting the child. Previously that signal was swallowed, and doppler went on to start the new child anyway. Now doppler exits as soon as no child is running, without starting another one. It removes the secrets mount, then dies by the same signal it received, so the shell sees the usual 128 plus the signal number. SIGQUIT is the exception and exits with 131: re-raising it would make the Go runtime dump every goroutine.Most of the diff is tests.
pkg/cmd/run_signals_test.gobuilds the CLI, pointsdoppler runat an httptest mock of the secrets download and watch endpoints, and sends real signals. A signal goes either to doppler alone or to its whole process group, which is what a terminal does on Ctrl-C. The child is a small shell script that logs each signal it receives. The suite pins today's behavior: forwarding with the flag on, off, and at its default under both a TTY and a pipe; both--commandand argument forms; exit codes; mount cleanup; repeated Ctrl-C; and the window while secrets are loading. All of these tests pass against a binary built frommaster. The restart cases fail onmasterand pass here, as doesTestSignalsNotInterceptedAfterWaitCommandinpkg/utils, which reproduces the original login bug. SettingDOPPLER_TEST_BINARYruns the suite against any prebuilt binary, which is how the comparison withmasterwas done. The TTY cases use a Linux pseudo-terminal and skip elsewhere, and the whole suite is excluded on Windows.Stack created with GitHub Stacks CLI • Give Feedback 💬