Tencent/WeKnora · error

oauth attempt not found or expired

Error message

oauth attempt not found or expired

What it means

CompleteAttempt marks an OAuth authorization attempt as completed after the token exchange succeeded. In Redis-backed mode it looks up the attempt key; redis.Nil (missing key) yields this error. The attempt either never existed, already completed and was consumed, or its TTL expired.

Source

Thrown at internal/mcp/oauth_state.go:131

		_, err = pipe.Exec(ctx)
		return err
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	expiresAt := time.Now().Add(oauthStateTTL)
	s.mem[state] = memStateEntry{value: value, expiresAt: expiresAt}
	s.attempts[state] = memAttemptEntry{value: attempt, expiresAt: expiresAt}
	return nil
}

// CompleteAttempt marks an authorization attempt complete only after the code
// exchange has successfully persisted a token.
func (s *oauthStateStore) CompleteAttempt(ctx context.Context, state string) error {
	if s.rdb != nil {
		data, err := s.rdb.Get(ctx, s.attemptKey(state)).Bytes()
		if err != nil {
			if err == redis.Nil {
				return fmt.Errorf("oauth attempt not found or expired")
			}
			return err
		}
		var attempt OAuthAttempt
		if err := json.Unmarshal(data, &attempt); err != nil {
			return err
		}
		attempt.Completed = true
		data, err = json.Marshal(attempt)
		if err != nil {
			return err
		}
		return s.rdb.Set(ctx, s.attemptKey(state), data, oauthStateTTL).Err()
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	entry, ok := s.attempts[state]
	if !ok || time.Now().After(entry.expiresAt) {

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Restart the OAuth flow: generate a new state and send the user through authorization again.
  2. Confirm the callback is arriving within oauthStateTTL; increase TTL if flows are legitimately slow.
  3. Verify the callback handler and CompleteAttempt use the same Redis instance and key namespace.
  4. Make callbacks idempotent: if the attempt is missing but a token was already stored, treat completion as done instead of erroring.

Example fix

// before
if err := store.CompleteAttempt(ctx, state); err != nil { return err }
// after
if err := store.CompleteAttempt(ctx, state); err != nil {
    if _, tokErr := store.Attempt(ctx, state); tokErr != nil {
        return redirectUserToRestartOAuthFlow() // stale/expired state
    }
    return err
}
Defensive patterns

Strategy: retry

Validate before calling

if err := rdb.Exists(ctx, "mcp:oauth:attempt:"+state).Err(); err != nil || rdb.Exists(ctx, "mcp:oauth:attempt:"+state).Val() == 0 {
    // state gone; restart flow instead of completing
}

Try / catch

if err := store.CompleteAttempt(ctx, state); err != nil {
    if strings.Contains(err.Error(), "not found or expired") {
        return restartAuthorization(ctx, principal) // fresh state
    }
    return err
}

Prevention

When it happens

Trigger: CompleteAuthorization calls CompleteAttempt with a state whose Redis key attemptKey(state) no longer exists (expired TTL, eviction, flushed Redis, or state never created via the store).

Common situations: User takes longer than the oauthStateTTL to finish login; Redis restarted without persistence; callback delivered twice and the second completion finds the key gone; deployment pointed at a different Redis instance than the one that issued the state.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/9cb52b9471746d73. Report an issue: GitHub.