plandex-ai/plandex · warning

context canceled while waiting to retry: %w

Error message

context canceled while waiting to retry: %w

What it means

While waiting between lock retries, retryWithExponentialBackoff selects on ctx.Done(); if the caller's context is canceled or times out during the backoff sleep it returns 'context canceled while waiting to retry' wrapping ctx.Err(). This means lock acquisition was abandoned by the caller, not that the lock was unavailable forever.

Source

Thrown at app/server/db/locks.go:592

	}

	// Exponential delay: initialRetryDelay * 2^(attempt)
	backoff := time.Duration(float64(initialLockRetryDelay) * math.Pow(backoffFactor, float64(attempt)))
	// Add jitter: ± jitterFraction
	jitterRange := time.Duration(float64(backoff) * jitterFraction)
	jitter := time.Duration(rand.Int63n(int64(jitterRange)*2)) - jitterRange

	wait := backoff + jitter
	if wait < 0 {
		wait = 0
	}

	log.Printf("[Lock][Retry][%d] Lock/transaction conflict (attempt #%d). Retrying in %s... (cause: %v)", getGoroutineID(), attempt, wait, cause)

	select {
	case <-ctx.Done():
		log.Printf("[Lock][Retry][%d] Context canceled while waiting to retry: %v", getGoroutineID(), ctx.Err())
		return "", fmt.Errorf("context canceled while waiting to retry: %w", ctx.Err())
	case <-time.After(wait):
		// Proceed with the next attempt.
	}

	return nextCall(attempt + 1)
}

func retryDeleteLock(ctx context.Context, cause error, attempt int, nextCall func(int) error) error {
	if attempt >= maxDeleteRetries {
		return fmt.Errorf("delete lock failed after 10 attempts: %w", cause)
	}
	// retry 10 times, no backoff or maybe a tiny 50ms
	select {
	case <-ctx.Done():
		return ctx.Err()
	case <-time.After(deleteRetryDelay):
	}
	return nextCall(attempt + 1)

View on GitHub (pinned to e2d772072e)

Solutions

  1. Increase the caller's context deadline to cover the worst-case backoff total (sum of initialLockRetryDelay * 2^attempt up to maxLockRetries)
  2. Treat this as an expected cancellation: unwrap with errors.Is(err, context.DeadlineExceeded/Canceled) and report as 'operation canceled' not a lock bug
  3. Check whether a proxy/load balancer cancels requests prematurely
  4. Reduce backoff delays if they routinely exceed the request budget

Example fix

// before
ctx := context.Background()
id, err := lockRepoDB(ctx, orgId, planId, reason)
// after
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
id, err := lockRepoDB(ctx, orgId, planId, reason)
if err != nil && errors.Is(err, context.DeadlineExceeded) {
    return fmt.Errorf("timed out waiting for plan lock (canceled while retrying)")
}
Defensive patterns

Strategy: try-catch

Validate before calling

totalBackoff := time.Duration(0)
for i := 0; i < maxLockRetries; i++ {
    totalBackoff += time.Duration(float64(initialLockRetryDelay) * math.Pow(backoffFactor, float64(i)))
}
if deadline, ok := ctx.Deadline(); !ok || time.Until(deadline) < totalBackoff {
    return errors.New("context deadline too short for lock retry budget")
}

Type guard

func isCtxCanceledDuringRetry(err error) bool {
    return err != nil && strings.Contains(err.Error(), "context canceled while waiting to retry")
}

Try / catch

id, err := lockRepoDB(ctx, orgId, planId, reason)
if err != nil {
    if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
        return fmt.Errorf("canceled while waiting for plan lock: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Caller's context deadline exceeded during an exponential backoff wait; HTTP request canceled by the client; server shutdown propagating a cancel through the context tree; parent context canceled after an upstream timeout.

Common situations: Request timeout set shorter than total backoff time (initialLockRetryDelay * 2^attempts); user cancels a long-running plan operation; graceful shutdown cancels in-flight lock acquisition.

Related errors


AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05). Data as JSON: /api/errors/47ae56131c0bdd01. Report an issue: GitHub.