From d7414b7bfb5cf04d97eeac9cc0455cdc554c7f5d Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 4 May 2026 19:12:59 +0200 Subject: [PATCH] Octopus DE: stop retrying on permanent auth failure (#29631) --- tariff/octopusde.go | 6 ++++++ tariff/octopusde/graphql/tokensource.go | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/tariff/octopusde.go b/tariff/octopusde.go index 0f56822cc..ba0313e7b 100644 --- a/tariff/octopusde.go +++ b/tariff/octopusde.go @@ -15,6 +15,9 @@ import ( "github.com/jinzhu/now" ) +// ErrAuthFailed re-exports the GraphQL auth-failure sentinel for use in tests. +var ErrAuthFailed = octoDeGql.ErrAuthFailed + type OctopusDe struct { log *util.Logger gqlClient *octoDeGql.OctopusDeGraphQLClient @@ -93,6 +96,9 @@ func (t *OctopusDe) run(done chan error) { if err := backoff.Retry(func() error { agr, err := t.gqlClient.ActiveAgreement() if err != nil { + if errors.Is(err, octoDeGql.ErrAuthFailed) { + return backoff.Permanent(err) + } return backoffPermanentError(err) } rates, err = ratesForAgreement(agr, time.Now()) diff --git a/tariff/octopusde/graphql/tokensource.go b/tariff/octopusde/graphql/tokensource.go index 47bc8ec28..a5c69af90 100644 --- a/tariff/octopusde/graphql/tokensource.go +++ b/tariff/octopusde/graphql/tokensource.go @@ -2,6 +2,7 @@ package graphql import ( "context" + "errors" "fmt" "time" @@ -12,6 +13,10 @@ import ( "golang.org/x/oauth2" ) +// ErrAuthFailed indicates the Kraken API rejected the supplied credentials. +// Callers should treat this as permanent and stop retrying to avoid account lockouts. +var ErrAuthFailed = errors.New("authentication failed") + type tokenSource struct { log *util.Logger email, password string @@ -34,6 +39,14 @@ func (ts *tokenSource) Token() (*oauth2.Token, error) { "email": ts.email, "password": ts.password, }); err != nil { + // Any GraphQL error response from obtainKrakenToken is an application-level + // rejection (bad credentials, account locked, etc.) — repeating the request + // will not change the outcome and continued retries can lock the account. + // Network/transport failures don't surface as graphql.Errors and stay + // transient via the wrapped path below. + if _, ok := errors.AsType[graphql.Errors](err); ok { + return nil, fmt.Errorf("%w: %w", ErrAuthFailed, err) + } return nil, fmt.Errorf("authentication failed: %w", err) }