Skip to content

feat: distinguish soft and hard errors in flow updates#7

Merged
appleboy merged 2 commits intomainfrom
tui
Feb 26, 2026
Merged

feat: distinguish soft and hard errors in flow updates#7
appleboy merged 2 commits intomainfrom
tui

Conversation

@appleboy
Copy link
Member

  • Introduce a Fallback field to FlowUpdate to distinguish soft errors (recoverable, trigger Device Code Flow) from hard errors (non-recoverable, surface to user).
  • Update error handling logic to use the Fallback field instead of matching error messages.
  • Add detailed documentation and comments clarifying error classes and flow control.
  • Implement tests to verify correct handling and routing of soft and hard errors using the Fallback field.

- Introduce a Fallback field to FlowUpdate to distinguish soft errors (recoverable, trigger Device Code Flow) from hard errors (non-recoverable, surface to user).
- Update error handling logic to use the Fallback field instead of matching error messages.
- Add detailed documentation and comments clarifying error classes and flow control.
- Implement tests to verify correct handling and routing of soft and hard errors using the Fallback field.

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Copilot AI review requested due to automatic review settings February 26, 2026 10:49
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an explicit Fallback signal to FlowUpdate so the browser OAuth flow can distinguish recoverable (“soft”) failures that should trigger a silent fallback to Device Code Flow from non-recoverable (“hard”) failures that should be surfaced to the user.

Changes:

  • Introduces FlowUpdate.Fallback with documentation describing soft vs hard StepError handling.
  • Updates the Bubble Tea browser model to route StepError based on Fallback instead of error-message matching.
  • Updates browser flow update emission to mark soft errors with Fallback: true and adds tests around the new behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
tui/messages.go Adds Fallback to FlowUpdate and documents how it classifies StepError messages.
tui/browser_model.go Switches StepError handling to use the Fallback flag instead of matching message strings.
browser_flow.go Emits StepError updates for more exit paths and sets Fallback: true for soft errors (openBrowser failure, callback timeout).
tui/manager_test.go Adds unit tests for the new Fallback field and for browser-model StepError routing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +126 to +132

t.Run("Fallback is ignored on non-error updates", func(t *testing.T) {
update := FlowUpdate{Type: StepStart, Step: 1, Fallback: false}
if update.Fallback {
t.Error("Fallback should not be set on non-error updates")
}
})
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The subtest "Fallback is ignored on non-error updates" is currently tautological (it sets Fallback: false and asserts it is false) and doesn’t verify any behavior. If the intent is to ensure non-error updates don’t trigger fallback handling, set Fallback: true and assert the consumer logic (e.g., handleFlowUpdate for StepStart) does not treat it as a fallback signal, or remove/rename this test.

Suggested change
t.Run("Fallback is ignored on non-error updates", func(t *testing.T) {
update := FlowUpdate{Type: StepStart, Step: 1, Fallback: false}
if update.Fallback {
t.Error("Fallback should not be set on non-error updates")
}
})

Copilot uses AI. Check for mistakes.
Comment on lines 156 to 167
case StepError:
m.err = fmt.Errorf("%s", update.Message)
if update.Message == "Browser authorization timed out" ||
update.Message == "Could not open browser: exit status 1" {
// Fallback to device flow
if update.Fallback {
// Soft error — caller requested fall back to Device Code Flow.
return func() tea.Msg {
return successMsg{storage: nil, ok: false}
}
}
// Hard error — surface it to the user.
return func() tea.Msg {
return errorMsg{err: m.err}
}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

In the StepError branch you set m.err before checking update.Fallback. For a soft error (Fallback=true) this leaves m.err non-nil, so renderBrowserComplete() will take the error path and display an authentication failure instead of the fallback-to-device-flow message. Set m.err only for hard errors, or explicitly clear it when handling a fallback StepError.

Copilot uses AI. Check for mistakes.
// etc.) — surface it to the user.
updates <- tui.FlowUpdate{
Type: tui.StepError,
Message: fmt.Sprintf("Authentication failed: %v", err),
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

This StepError update message includes the prefix "Authentication failed:", but the browser completion view also prefixes errors with "Authentication failed:". This can lead to duplicated text in the TUI (e.g., "Authentication failed: Authentication failed: …"). Prefer sending the raw error string here, or adjust the rendering to avoid double-prefixing.

Suggested change
Message: fmt.Sprintf("Authentication failed: %v", err),
Message: err.Error(),

Copilot uses AI. Check for mistakes.
- Improve error message rendering by using only the error string.
- Add tests for SimpleManager browser flow, ensuring correct fallback signaling and update handling.

Signed-off-by: appleboy <appleboy.tw@gmail.com>
@appleboy appleboy merged commit e617543 into main Feb 26, 2026
12 of 16 checks passed
@appleboy appleboy deleted the tui branch February 26, 2026 12:37
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