Skip to content

Add timeouts and bounded reconnect to Redis client#120

Open
masnwilliams wants to merge 1 commit into
mainfrom
hypeship/redis-timeouts
Open

Add timeouts and bounded reconnect to Redis client#120
masnwilliams wants to merge 1 commit into
mainfrom
hypeship/redis-timeouts

Conversation

@masnwilliams

@masnwilliams masnwilliams commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

The Redis client retried reconnecting forever with no connect or command timeout. When Redis was unreachable, any command (e.g. the OAuth org-context writes in /token) blocked until the serverless function limit instead of throwing — so kernel login hung after the browser showed "authentication successful" rather than failing with an error.

This bounds the failure:

  • Bounded reconnectreconnectStrategy now returns an Error after MAX_RECONNECT_ATTEMPTS (10) instead of retrying indefinitely, so queued commands reject when Redis stays down.
  • Connect timeoutconnectTimeout (5s) on the socket so a black-holed connection fails fast.
  • Per-command timeout — every op runs through withTimeout (5s ceiling) so a stalled-but-connected command surfaces as an error.

Net effect: a Redis outage returns a clean error in seconds instead of hanging the caller.

Test plan

  • tsc --noEmit passes
  • prettier clean
  • Manual: confirm normal OAuth login still succeeds against a healthy Redis (behavior unchanged on the happy path)

Follow-up companion PR on kernel/cli adds a timeout on the CLI token exchange so it also fails fast.


Note

Medium Risk
Touches shared Redis plumbing used by OAuth token/authorize flows; mis-tuned timeouts could cause false failures under slow Redis, but behavior on healthy Redis should match prior reconnect/retry semantics.

Overview
When Redis is unreachable, OAuth-related calls (e.g. org-context writes on /token) could block until the serverless function limit instead of failing—so CLI login could hang after the browser showed success.

src/lib/redis.ts now caps failure time: 5s socket connectTimeout, reconnectStrategy stops after 10 attempts (returns an Error instead of retrying forever), and withTimeout wraps every command in withReconnect with a 5s ceiling. Transient socket errors still get one reconnect + retry, but both attempts are timeout-bounded.

Happy path against healthy Redis is unchanged; outages should surface as errors within seconds rather than indefinite hangs.

Reviewed by Cursor Bugbot for commit 3397557. Bugbot is set up for automated code reviews on this repo. Configure here.

Previously the Redis client retried reconnecting forever with no connect or
command timeout, so an unreachable Redis made callers (e.g. the OAuth token
exchange) block indefinitely instead of failing. Bound the reconnect attempts,
set a connect timeout, and cap each command so Redis outages surface as errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mcp Ready Ready Preview, Comment Jul 13, 2026 8:28pm

@masnwilliams masnwilliams marked this pull request as ready for review July 13, 2026 20:45
@masnwilliams masnwilliams requested a review from sjmiller609 July 13, 2026 20:46

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3397557. Configure here.

Comment thread src/lib/redis.ts
} finally {
if (timer) clearTimeout(timer);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timeout leaves unhandled rejection

Medium Severity

The withTimeout function uses Promise.race but doesn't manage the underlying Redis operation when the timeout wins. This can lead to unhandled promise rejections and unintended Redis mutations if the original operation completes later.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3397557. Configure here.

Comment thread src/lib/redis.ts
async function withReconnect<T>(operation: () => Promise<T>): Promise<T> {
try {
return await operation();
return await withTimeout(operation);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Connect await exceeds command timeout

High Severity

The new withTimeout wrapper only covers the Redis command inside withReconnect, while every exported helper still awaits ensureConnected() first. On a cold instance with Redis down, that connect/reconnect loop can run many connectTimeout cycles plus backoff before reconnectStrategy gives up, so OAuth paths like /token can keep blocking far beyond the 5s command ceiling this PR adds.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3397557. Configure here.

Comment thread src/lib/redis.ts
);
}
return Math.min(500 + retries * 100, 2000);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reconnect limit off by one

Low Severity

reconnectStrategy stops only when retries > MAX_RECONNECT_ATTEMPTS (10), so node-redis can still schedule backoff for retries 0 through 10 before returning an error. That is one more delayed reconnect cycle than MAX_RECONNECT_ATTEMPTS and the error text imply.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3397557. Configure here.

@vercel vercel Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Suggestion:

The 5s command timeout does not cover ensureConnected()/client.connect(), so an unreachable Redis blocks the caller for ~60s+ instead of the intended ~5s budget.

Fix on Vercel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant