Repository navigation
fix(typescript-client): prevent handle -next suffix accumulation on repeated 409s - #3961
Merged
Merged
Conversation
…epeated 409s When a proxy strips the handle header from 409 responses, the client falls back to appending "-next" to the current handle as a cache buster. On repeated retries this compounds (handle-next-next-next-next...) until the URL exceeds server URI limits (414), cascading into 5xx errors. Strip any existing -next chain before appending a single -next suffix, keeping the cache-busting behavior bounded. Fixes ELECTRIC-4GV Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
commit: |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3961 +/- ##
=======================================
Coverage 88.77% 88.78%
=======================================
Files 25 25
Lines 2415 2425 +10
Branches 604 612 +8
=======================================
+ Hits 2144 2153 +9
- Misses 269 270 +1
Partials 2 2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
- Simplify regex from /-next((-next)*)$/ to /(-next)+$/ - Add console.warn when 409 response lacks handle header - Strengthen test assertion to check exactly 1 -next suffix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Covers ELECTRIC-4GV Pattern A where the client never received an initial handle. Verifies that "undefined" is never stringified into the handle parameter and that -next does not accumulate. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The -next suffix approach conflated the handle (an identity) with cache busting (a URL uniqueness concern). It also didn't produce unique URLs across retries since "handle-next" was the same every time. Now when a 409 response lacks a handle header, we: 1. Reset without fabricating a handle 2. Set a one-shot random cache-buster query param (same mechanism used by StaleRetryState) to ensure the retry URL is unique Tests now assert the real invariant: all retry URLs must be unique, and no URL should contain "-next" or "undefined" in any parameter. Also adds note to AGENTS.md to always read SPEC.md before working on the typescript client. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… path Tests the state machine contract that markMustRefetch with no handle produces InitialState with undefined handle, offset -1, and omits the handle query param from URLs. Algebraic test covers all 7 states. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
thruflo
approved these changes
Mar 5, 2026
Contributor
|
This PR has been released! 🚀 The following packages include changes from this PR:
Thanks for contributing to Electric! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix unbounded URL growth on 409 retries when proxy strips the handle header, which caused 414 URI Too Long errors and cascading 5xx failures.
Root Cause
When the server returns a 409 "must-refetch" response, the client needs a new shape handle. If the response includes the
electric-handleheader, the client uses it directly. If not (e.g., a proxy/CDN strips the header), the old code appended-nextto the current handle as a cache buster. This violated two invariants:-nextconflated identity with URL uniquenesshandle-nextwas the same on every retry, only busting the cache once. And it grew unboundedly:handle-next-next-next-next...Two patterns were observed in production (ELECTRIC-4GV, 8,764 events in 9 hours):
expired_handle=undefined-next-next-next...— handle was never initializedhandle=87840909-...-next-next-next...— valid handle that spiraledApproach
Removed the
-nextsuffix hack entirely. When a 409 lacks a handle header:#reset(undefined)viamarkMustRefetch, preserving the state machine's contract (per SPEC.md I10)cache-busterquery param — the sameCACHE_BUSTER_QUERY_PARAMmechanism already used byStaleRetryStatefor stale CDN responsesThe cache buster is consumed once in
#constructUrland cleared, ensuring each retry gets a unique URL without polluting the handle.Key Invariants
cache-busterparam (tested)-next, noundefinedstringificationmarkMustRefetch(handle?)produces InitialState per SPEC.md I10#requestShape(line 890) andfetchSnapshot(line 1886)Non-goals
Verification
Tests assert the real invariants:
-nextorundefinedin any parameterFiles Changed
packages/typescript-client/src/client.ts-nexthack with#refetchCacheBusterat both 409 paths + consumption in#constructUrlpackages/typescript-client/test/expired-shapes-cache.test.tsAGENTS.md.changeset/fix-handle-next-accumulation.md🤖 Generated with Claude Code