Skip to content

fix: guard resubscribeDelay against inputs that would panic - #154

Merged
reneaaron merged 1 commit into
mainfrom
fix/resubscribe-delay-clamp
Jul 31, 2026
Merged

fix: guard resubscribeDelay against inputs that would panic#154
reneaaron merged 1 commit into
mainfrom
fix/resubscribe-delay-clamp

Conversation

@reneaaron

Copy link
Copy Markdown
Member

Follow-up to #153, from reviewing it after merge.

The problem

resubscribeDelay(attempt) shifts by attempt-1, so any attempt below 1 shifts by a negative amount — which is a runtime panic, not a bad return value:

attempt=1 -> 15s
attempt=0 -> panic: runtime error: negative shift amount

rand.Int63n has the same shape: it panics on a non-positive argument, so setting resubscribeBaseDelay = 0 to disable jitter would panic rather than degrade to no delay.

Why guard it rather than document it

Neither is reachable today — the only callers pass a literal 1, or are guarded by attempt > 0. But that guard lives in startPersistentSubscription, a different function from the arithmetic it protects. A new caller, or a refactor that moves the check, reintroduces it silently.

The blast radius is what makes it worth a clamp. An unrecovered panic in a goroutine takes down the whole process. http-nostr runs a single replica, so every subscription drops at once and reconnects together when it restarts — the synchronised burst #153 exists to prevent. A one-line arithmetic slip would undo the fix it lives inside.

Both bounds now belong to the function that needs them.

Test

TestResubscribeDelayHandlesAttemptsBelowOne covers 0, -1, -1000. Verified it's a real regression test, not a vacuous one — with the clamp removed:

--- FAIL: TestResubscribeDelayHandlesAttemptsBelowOne
    resubscribeDelay(0) panicked: runtime error: negative shift amount

and it passes with the clamp restored. Full suite green, gofmt/go build/go vet clean.

Deploy note

Worth landing before the deployment pin is moved off the branch build, so production goes from fix-stagger-resubscribe-5d15f56 to a main- tag once rather than twice.

resubscribeDelay(attempt) shifts by attempt-1, so any attempt below 1 shifts
by a negative amount, which is a runtime panic rather than a bad return
value:

  resubscribeDelay(0) -> runtime error: negative shift amount

rand.Int63n has the same shape: it panics on a non-positive argument, so
setting resubscribeBaseDelay to 0 to disable jitter would panic instead of
degrading to no delay.

Neither is reachable today. The only callers pass a literal 1 or are guarded
by `attempt > 0`, but that guard sits in startPersistentSubscription, a
different function from the arithmetic it protects, so a new caller or a
refactor that moves the check reintroduces it silently.

Worth guarding rather than documenting because of the blast radius. An
unrecovered panic in a goroutine takes down the process, and every
subscription reconnects at once when it comes back - the synchronised burst
this backoff exists to prevent.

Both bounds now belong to the function that needs them. Added a test that
fails with "negative shift amount" without the clamp.

Claude-Session: https://claude.ai/code/session_01NzMsjNwuQU9gwwcbUvpL5G
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@reneaaron, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c33667c4-3946-4929-b6cd-9a7ed882b5b4

📥 Commits

Reviewing files that changed from the base of the PR and between 0d4d179 and 03ebc77.

📒 Files selected for processing (2)
  • internal/nostr/subscriptions.go
  • internal/nostr/subscriptions_test.go

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@reneaaron
reneaaron marked this pull request as ready for review July 31, 2026 13:35
@reneaaron
reneaaron merged commit 36db5eb into main Jul 31, 2026
4 checks passed
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