fix(login): retry transient poll errors instead of aborting the device flow
A proxy 502 during a backend restart killed attended logins mid-poll. NotFound/InvalidArgument still fail fast; everything else retries until the device-code deadline, with a one-time notice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
2b685348c9
commit
4e82b1ef41
@ -38,11 +38,19 @@ func newLoginCmd() *cobra.Command {
|
||||
fmt.Printf("Visit %s?user_code=%s to authorize.\n", start.Msg.VerificationUri, start.Msg.UserCode)
|
||||
interval := time.Duration(start.Msg.IntervalSeconds) * time.Second
|
||||
deadline := time.Now().Add(time.Duration(start.Msg.ExpiresInSeconds) * time.Second)
|
||||
warned := false
|
||||
for time.Now().Before(deadline) {
|
||||
time.Sleep(interval)
|
||||
poll, err := cli.Auth.PollDevice(ctx, connect.NewRequest(&v1.PollDeviceRequest{DeviceCode: start.Msg.DeviceCode}))
|
||||
if err != nil {
|
||||
return err
|
||||
if !pollRetryable(err) {
|
||||
return err
|
||||
}
|
||||
if !warned {
|
||||
fmt.Printf("Temporary error while waiting for approval (will keep trying): %v\n", err)
|
||||
warned = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
switch poll.Msg.Status {
|
||||
case "pending":
|
||||
@ -82,6 +90,18 @@ func newLoginCmd() *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
// pollRetryable reports whether a PollDevice error is worth retrying until
|
||||
// the device-code deadline. Transient transport failures (a proxy 502 while
|
||||
// the backend restarts, timeouts) must not abort an attended login; NotFound
|
||||
// and InvalidArgument mean the server can never approve this code.
|
||||
func pollRetryable(err error) bool {
|
||||
switch connect.CodeOf(err) {
|
||||
case connect.CodeNotFound, connect.CodeInvalidArgument:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func newWhoamiCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "whoami",
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
|
||||
"git.dev.alexdunmow.com/block/cli/internal/creds"
|
||||
)
|
||||
|
||||
@ -25,3 +28,18 @@ func TestApplyPolledAccount_EmptyFallsThrough(t *testing.T) {
|
||||
t.Fatalf("hc = %+v, want untouched", hc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPollRetryable(t *testing.T) {
|
||||
if pollRetryable(connect.NewError(connect.CodeNotFound, errors.New("unknown device code"))) {
|
||||
t.Error("NotFound must not be retried — the server no longer knows the code")
|
||||
}
|
||||
if pollRetryable(connect.NewError(connect.CodeInvalidArgument, errors.New("device_code is required"))) {
|
||||
t.Error("InvalidArgument must not be retried")
|
||||
}
|
||||
if !pollRetryable(connect.NewError(connect.CodeUnavailable, errors.New("unavailable: 502 Bad Gateway"))) {
|
||||
t.Error("Unavailable (proxy 502 during backend restart) must be retried")
|
||||
}
|
||||
if !pollRetryable(errors.New("plain network error")) {
|
||||
t.Error("non-connect errors must be retried")
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user