fix(sdk): authorize() no longer stacks request interceptors#390
Merged
Conversation
When authorize(client, fn) was called more than once on the same axios client, each call registered a new request interceptor without ejecting prior ones. Because axios runs request interceptors in REVERSE registration order, the OLDEST token won at request time -- exactly the opposite of what callers expect when re-authorizing a singleton client. Real-world impact: a cross-org sync flow in configuration-hub-api that re-authorized the entity client (source token then target token) wrote every "create" to the SOURCE org. The API returned 201s with org_id=sourceOrg, and the orchestrator silently stored phantom target ids that 404 on lookup. Fix: track the previously-installed auth interceptor id on the client via a Symbol.for marker and eject it before installing the new one. Also clear the static defaults.headers.common.authorization when switching into function-form so the two forms can't race. Regression coverage added in __tests__/authorize.test.ts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com>
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.
Summary
authorize(client, fn)registered a new request interceptor on every call without ever ejecting the prior one. Because axios runs request interceptors in reverse registration order, the oldest token always won at request time — exactly the opposite of what callers expect when they re-authorize a singleton client.Real-world bug this fixes
We just hit this in
configuration-hub-api's cross-org sync feature:authorize(entityClient, () => sourceToken)authorize(entityClient, () => targetToken)201withorg_id=sourceOrg; the orchestrator stored phantomtarget_idvalues that 404 on lookupConfirmed via direct
curl: the target token writes correctly to the target org when used standalone. The bug is purely in the interceptor stacking.What changed
Symbol.for('@epilot/sdk:authInterceptorId')markerdefaults.headers.common.authorizationleft over from a previous string-form call so the two forms cannot raceTests
Added
packages/epilot-sdk-v2/__tests__/authorize.test.ts(8 new tests, using realaxios.create()and the publicinterceptors.request.handlersarray — not mocks):authorize()calls do not grow the handler listTest plan
pnpm testpasses (318 tests, +8 new)pnpm buildsucceedspnpm lintclean🤖 Generated with Claude Opus 4.7 (1M context)