Summary
hookdeck login opens a browser window and blocks indefinitely when there is no terminal, instead of failing fast with guidance.
Reproduce
With an empty config and no TTY:
hookdeck login --hookdeck-config <empty.toml> </dev/null # stdout+stderr redirected
Output: Press Enter to open the browser (^C to quit)Waiting for confirmation...
It then opens a real browser window on the user's desktop and blocks. Killed at 60s with no sign of stopping on its own. This happened to a maintainer during testing.
Root cause
pkg/login/client_login.go:
Login() has the headless guard, but only inside if config.Profile.APIKey != "" (~line 62):
needsStdin := !isSSH() && canOpenBrowser()
if !stdinIsTerminal() && needsStdin { return ErrRejectedKeyNoTerminal }
- With an empty config that entire block is skipped, so execution reaches
waitForLoginSession() unguarded.
- There (~line 120) the else-branch does
fmt.Printf("Press Enter to open the browser (^C to quit)"), then fmt.Fscanln(input), then openBrowser(...). With stdin at /dev/null, Fscanln returns instantly on EOF and the browser opens anyway.
The comment above the existing guard reads "mirroring the branch in waitForLoginSession" — so the branch was known about, and only the rejected-key path was guarded.
The isSSH() || !canOpenBrowser() branch prints a URL and polls without reading stdin. That works headlessly and must keep working.
Why this matters
Any CI job, Dockerfile, nohup script or coding agent that runs hookdeck login hangs indefinitely and spawns browser processes.
It also means the v2.5.0 release-note claim for #337 — "Unauthenticated commands no longer hang for four minutes… Without a terminal it now exits immediately with instructions" — was never true for hookdeck login itself.
Two sub-issues on the same line
Press Enter to open the browser (^C to quit) has no trailing newline, so it runs straight into Waiting for confirmation....
- It advertises
^C to quit while holding no terminal that can deliver ^C.
Suggested fix
Apply the same guard on the fresh-login path, with an error matching the quality of the existing rejected-key message — naming hookdeck ci --api-key, hookdeck login --cli-key, and HOOKDECK_API_KEY as the ways forward.
Testing
Unit: pkg/login/client_login_test.go already stubs stdinIsTerminal and canOpenBrowser. Cover no-key + no-terminal + browser-capable → fast error and no browser; no-key + no-terminal + SSH → still prints the URL and polls.
Related
Filed by Claude on Phil's behalf, from release-candidate testing.
Summary
hookdeck loginopens a browser window and blocks indefinitely when there is no terminal, instead of failing fast with guidance.Reproduce
With an empty config and no TTY:
Output:
Press Enter to open the browser (^C to quit)Waiting for confirmation...It then opens a real browser window on the user's desktop and blocks. Killed at 60s with no sign of stopping on its own. This happened to a maintainer during testing.
Root cause
pkg/login/client_login.go:Login()has the headless guard, but only insideif config.Profile.APIKey != ""(~line 62):waitForLoginSession()unguarded.fmt.Printf("Press Enter to open the browser (^C to quit)"), thenfmt.Fscanln(input), thenopenBrowser(...). With stdin at/dev/null,Fscanlnreturns instantly on EOF and the browser opens anyway.The comment above the existing guard reads "mirroring the branch in waitForLoginSession" — so the branch was known about, and only the rejected-key path was guarded.
The
isSSH() || !canOpenBrowser()branch prints a URL and polls without reading stdin. That works headlessly and must keep working.Why this matters
Any CI job, Dockerfile,
nohupscript or coding agent that runshookdeck loginhangs indefinitely and spawns browser processes.It also means the v2.5.0 release-note claim for #337 — "Unauthenticated commands no longer hang for four minutes… Without a terminal it now exits immediately with instructions" — was never true for
hookdeck loginitself.Two sub-issues on the same line
Press Enter to open the browser (^C to quit)has no trailing newline, so it runs straight intoWaiting for confirmation....^C to quitwhile holding no terminal that can deliver^C.Suggested fix
Apply the same guard on the fresh-login path, with an error matching the quality of the existing rejected-key message — naming
hookdeck ci --api-key,hookdeck login --cli-key, andHOOKDECK_API_KEYas the ways forward.Testing
Unit:
pkg/login/client_login_test.goalready stubsstdinIsTerminalandcanOpenBrowser. Cover no-key + no-terminal + browser-capable → fast error and no browser; no-key + no-terminal + SSH → still prints the URL and polls.Related
Filed by Claude on Phil's behalf, from release-candidate testing.