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
- Increase the caller's context deadline to cover the worst-case backoff total (sum of initialLockRetryDelay * 2^attempt up to maxLockRetries)
- Treat this as an expected cancellation: unwrap with errors.Is(err, context.DeadlineExceeded/Canceled) and report as 'operation canceled' not a lock bug
- Check whether a proxy/load balancer cancels requests prematurely
- 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
- Set the acquisition context deadline above the total possible backoff
- Expect cancellation during retries as a normal outcome, not a lock bug
- Propagate shutdown contexts so cancellation is intentional and logged
- Keep request-level timeouts larger than lock retry budgets
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
- context timeout
- failed to acquire lock after %d attempts: %w
- delete lock failed after 10 attempts: %w
- stream timeout due to inactivity: The AI model (%s/%s) is no
- Request was cancelled
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/47ae56131c0bdd01.
Report an issue: GitHub.