fix(login): keep a completed login when the keyring is missing, stop offering a browser there isn't - #2088
Open
jdx wants to merge 1 commit into
Open
fix(login): keep a completed login when the keyring is missing, stop offering a browser there isn't#2088jdx wants to merge 1 commit into
jdx wants to merge 1 commit into
Conversation
…offering a browser there isn't A device-code login on a headless SSH box printed the code, offered "[Enter] Open browser" (which could only produce `exec: "xdg-open": executable file not found`), and then — after the user had already approved in a browser elsewhere — exited 1 on `dbus-launch` not existing, telling them to re-run the whole flow with ENTIRE_TOKEN_STORE=file. Three fixes: - The default credential backend is now keyring-then-file. The keyring is still tried first, so a working one is always preferred and a machine that grows one goes back to using it; when it refuses the write the tokens land in the documented 0600 file instead of being discarded, and the fallback is announced on stderr (once per process), never silent. Reads follow, so later commands need no env var. Ctrl-C during a keyring call stays an abort and never writes a token to disk. ENTIRE_TOKEN_STORE=keyring is the new opt-out for "never put my bearer tokens on disk". - The URL prompt advertises only actions this machine can honour: an opener on $PATH plus a graphical session (or WSL bridge) for the browser, and clipboard.Unsupported for the copy hint. With neither, the prompt line disappears and the key reader never starts, leaving Ctrl-C native instead of taking a TTY into raw mode for keystrokes that could only be refused. Pressing Enter anyway is still honoured wherever an opener exists, and answered with a sentence where it isn't. - The non-interactive device flow left "Waiting for approval… " open, so the save error was glued onto it. The status line is now always closed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR improves entire login robustness on headless/keyring-less machines by (1) introducing a keyring→file tokenstore fallback so completed logins aren’t discarded, and (2) making the device-code URL prompt only advertise actions the current machine can actually perform (browser open / clipboard copy), while ensuring the “Waiting for approval…” status line is always newline-terminated.
Changes:
- Add a default keyring→file fallback credential store and document/extend
ENTIRE_TOKEN_STOREbehavior (file/keyring). - Gate interactive login URL prompt hints/actions on real browser/clipboard availability; avoid starting raw-mode key reads when no action is usable.
- Update README guidance for headless auth and add targeted unit tests for fallback store and login UX.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Updates headless authentication docs to reflect fallback-to-file behavior and env overrides. |
| internal/entireclient/tokenstore/tokenstore.go | Switches default backend to a fallback store; adds keyring override and updates backend provenance description. |
| internal/entireclient/tokenstore/fallback.go | New fallback store implementation (keyring first, file fallback) with one-time warning behavior. |
| internal/entireclient/tokenstore/fallback_test.go | New tests covering fallback store behavior, warning behavior, and env selection helpers. |
| cmd/entire/cli/login.go | Closes the non-interactive waiting line; introduces URL prompt action gating (browser/clipboard/keys) and improved Enter handling. |
| cmd/entire/cli/login_tty_test.go | Updates TTY-focused login tests for the new availableActions interactor interface. |
| cmd/entire/cli/login_test.go | Adds tests for prompt-line matrix, “no usable actions” behavior, Enter-without-opener behavior, and waiting-line newline closure. |
| cmd/entire/cli/browser_open_windows.go | Adds browserOpenerAvailable() for Windows (always true via ShellExecute). |
| cmd/entire/cli/browser_open_other.go | Adds opener availability detection for non-Windows and improves opener error messages. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+144
to
148
| // are rather than the keyring that refused them. | ||
| if path, ok := FellBackToFileStore(); ok { | ||
| return fmt.Sprintf("file %s (%s unavailable)", path, keyringProviderName()) | ||
| } | ||
| return keyringProviderName() |
Comment on lines
+164
to
+169
| // The keyring already failed earlier in this process; the warning has | ||
| // been printed and the file store is where this belongs. | ||
| if err := s.file.Set(service, user, password); err != nil { | ||
| return err | ||
| } | ||
| return nil |
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.
https://entire.io/gh/entireio/cli/trails/1116
The transcript this fixes
Three separate problems, in the order they hurt.
1. A login the user already completed was thrown away
The credential write happens after approval, so on a box with no D-Bus the user entered the code, approved in a browser on another machine — and then got
exit status 1plus instructions to re-run the entire flow withENTIRE_TOKEN_STORE=file.The default backend is now keyring-then-file (
internal/entireclient/tokenstore/fallback.go):0600file instead of being discarded, and the fallback is announced on stderr — once per process, never silent.ErrNotFound, so a broken keyring never masquerades as "not logged in".Deleteclears both backends, so logout removes every copy a fallback write may have left.On a keyring-less machine:
The one product judgment call here, flagged for a human: auto-fallback-with-a-warning over failing. Discarding an approved login seemed clearly worse than storing it in the location the README already documents — but it does mean a machine that silently lost its keyring starts writing tokens to disk.
ENTIRE_TOKEN_STORE=keyringis the new opt-out for "never put my bearer tokens on disk";=filestill skips the keyring entirely. Happy to make it a prompt instead if that's the wrong default.2. It offered a browser that doesn't exist
[Enter] Open browser [c] Copy URLwas advertised unconditionally, so on a headless SSH box the keystroke could only buy a nestedexec: \"xdg-open\"error — right after the CLI told the user to press it.The prompt now advertises only what the machine can honour: an opener on
\$PATHplus a graphical session (or a WSL bridge) for the browser, andclipboard.Unsupportedfor the copy hint. With neither available the prompt line disappears and the key reader never starts, which also leaves Ctrl-C native instead of taking a TTY into raw mode to collect keystrokes that could only be refused.This gates what is advertised, not a veto: pressing Enter is still honoured wherever the opener binary exists, so an unusual-but-working setup isn't locked out — and where it doesn't exist, the answer is a sentence rather than an exec error.
3. The error was glued to the status line
Waiting for approval... save login: store refresh token…— the non-interactive device path printedWaiting for approval…with no newline and never closed it, so whatever came next ran into it. The status line is now always closed.Also
tokenstorepackage doc describes the new arrangement.Testing
ErrNotFound, interrupt writes nothing, both-backends-failing (ErrFileFallbackFailed), delete across both backends, provenance inBackendDescription, and the env-var matrix.mise run fmt && mise run lintclean; unit, integration, and both canary suites pass. Verified the fallback end-to-end on a real keyring-less machine (warning, write, read-back, provenance, delete).🤖 Generated with Claude Code