You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: the generic-OAuth hop permit is not threaded to the adapter, so one replay can be charged twice #4709
The generic-OAuth 429 ladder reserves a hop from the shared request budget, and the reservation is the charge (src/server/responses/adapter-dispatch.ts:719). The same budget is then handed to the adapter for the replay (:420), and an adapter that reserves for itself charges again — Kiro reserves immediately before its physical send at src/adapters/kiro-retry.ts:171. The non-adapter transient helper has the same shape: the hop is not supplied through pendingHopPermit, so its onSendsConsumed reporter adds a second charge.
One physical replay can therefore be counted twice, or refused as a second target transition. The passthrough ladder already solves this by reserving with countedExternally: true and handing the permit down through pendingHopPermit (src/server/responses/passthrough-dispatch.ts:1115).
This predates 2.56.0 and was found while fixing a related permit leak in that release; it is the remaining half of the same accounting story.
Reproduction
Configure a generic-OAuth provider with two or more accounts and an adapter that reserves its own sends (Kiro), or any provider with a transient retry policy.
Make the first send return 429 so the roster rotates.
Watch the request's send accounting: the single replay is charged by the hop reservation and again by the adapter or by onSendsConsumed.
Suggested fix: mirror the passthrough shape — reserve the hop with countedExternally: true and pass it through pendingHopPermit so the layer that actually dispatches settles it.
이 이슈는 generic-OAuth 429 홉 예약이 어댑터/transient 쪽에 전달되지 않아 한 번의 물리 재전송이 두 번 청구될 수 있는 회계 버그다. 현재 tip 51d577c3f에서 src/server/responses/passthrough-dispatch.ts는 reserveCredentialHop(..., true)로 홉을 잡고 sendBudgetState.pendingHopPermit = hop.permit로 내려준 뒤 rebuild가 그 예약을 쓴다(약 1115–1144행대). 반면 src/server/responses/adapter-dispatch.ts의 generic-OAuth 429 사다리(약 719행대)는 reserveCredentialHop만 하고 pendingHopPermit 필드 자체가 이 파일에 없다. 같은 예산이 어댑터로 넘어가면 Kiro는 src/adapters/kiro-retry.ts 약 171행에서 물리 send 직전에 다시 reserveDispatch한다. transient helper의 onSendsConsumed도 홉 permit을 못 받으면 두 번째 청구를 남긴다.
증상은 early request_send_budget 거절이거나 send count가 실제 wire보다 크게 보이는 형태다. 2.56.0 회귀 감사에서 관련 permit 누수를 고친 뒤 남은 반쪽이라는 설명과, passthrough가 이미 정답 모양을 갖고 있다는 점이 tip 코드와 일치한다. #4546 send-budget 스택의 핵심 불변(물리 send당 정확히 한 번)을 어댑터 경로에서 깨므로 우선순위가 높다.
고치는 방법은 이슈 제안 그대로다. 홉을 countedExternally: true(또는 passthrough와 같은 세 번째 인자 true)로 예약하고, 실제 dispatch 층이 settle하도록 pendingHopPermit을 어댑터/transient 경로에 관통시킨다. Kiro·Cursor처럼 내부 ladder가 있는 어댑터로 회귀 테스트를 걸어야 한다.
경로 src/server/responses/adapter-dispatch.ts generic-OAuth 429 - 홉을 예약하지만 permit을 아래로 넘기지 않는다.
경로 src/server/responses/passthrough-dispatch.ts - pendingHopPermit로 같은 문제를 이미 해결한 참고 구현.
라인 src/adapters/kiro-retry.ts ~171 - 어댑터가 자체 reserve하여 이중 청구 가능.
경로 transient onSendsConsumed - 홉 permit 미전달 시 reporter가 한 번 더 센다.
메인테이너의 판단이 필요한 지점
adapter-dispatch에 passthrough와 동일한 pendingHopPermit 상태를 도입할지, 공통 helper로 뺄지.
이중 청구 회귀를 Kiro만으로 고정할지 Cursor transient도 넣을지.
2.57 필수 후속으로 올릴지.
너의 추천
받아라. passthrough 모양을 adapter-dispatch에 그대로 이식하고, Kiro 429 로스터 회전 통합 테스트로 ‘물리 1회 = 청구 1회’를 고정하라. types/config 스플릿과 무관, close-don't-rebase 대상 아님.
Activity
lidge-jun commentedon Sep 15, 2026
리뷰 · 우선순위 74 / 80
이 이슈는 generic-OAuth 429 홉 예약이 어댑터/transient 쪽에 전달되지 않아 한 번의 물리 재전송이 두 번 청구될 수 있는 회계 버그다. 현재 tip
51d577c3f에서src/server/responses/passthrough-dispatch.ts는reserveCredentialHop(..., true)로 홉을 잡고sendBudgetState.pendingHopPermit = hop.permit로 내려준 뒤 rebuild가 그 예약을 쓴다(약 1115–1144행대). 반면src/server/responses/adapter-dispatch.ts의 generic-OAuth 429 사다리(약 719행대)는reserveCredentialHop만 하고pendingHopPermit필드 자체가 이 파일에 없다. 같은 예산이 어댑터로 넘어가면 Kiro는src/adapters/kiro-retry.ts약 171행에서 물리 send 직전에 다시reserveDispatch한다. transient helper의onSendsConsumed도 홉 permit을 못 받으면 두 번째 청구를 남긴다.증상은 early
request_send_budget거절이거나 send count가 실제 wire보다 크게 보이는 형태다. 2.56.0 회귀 감사에서 관련 permit 누수를 고친 뒤 남은 반쪽이라는 설명과, passthrough가 이미 정답 모양을 갖고 있다는 점이 tip 코드와 일치한다. #4546 send-budget 스택의 핵심 불변(물리 send당 정확히 한 번)을 어댑터 경로에서 깨므로 우선순위가 높다.고치는 방법은 이슈 제안 그대로다. 홉을
countedExternally: true(또는 passthrough와 같은 세 번째 인자true)로 예약하고, 실제 dispatch 층이 settle하도록pendingHopPermit을 어댑터/transient 경로에 관통시킨다. Kiro·Cursor처럼 내부 ladder가 있는 어댑터로 회귀 테스트를 걸어야 한다.경로
src/server/responses/adapter-dispatch.tsgeneric-OAuth 429 - 홉을 예약하지만 permit을 아래로 넘기지 않는다.경로
src/server/responses/passthrough-dispatch.ts-pendingHopPermit로 같은 문제를 이미 해결한 참고 구현.라인
src/adapters/kiro-retry.ts~171 - 어댑터가 자체 reserve하여 이중 청구 가능.경로 transient
onSendsConsumed- 홉 permit 미전달 시 reporter가 한 번 더 센다.메인테이너의 판단이 필요한 지점
pendingHopPermit상태를 도입할지, 공통 helper로 뺄지.너의 추천
받아라. passthrough 모양을 adapter-dispatch에 그대로 이식하고, Kiro 429 로스터 회전 통합 테스트로 ‘물리 1회 = 청구 1회’를 고정하라. types/config 스플릿과 무관, close-don't-rebase 대상 아님.
이 댓글은 grok-bot이 작성했습니다
Merge pull request #4745 from lidge-jun/codex/bl1-hop-permit-charge
lidge-jun commentedon Sep 16, 2026
Landed via #4745 at cbc4c10.
fix(responses): settle a credential hop where the replay is dispatched (
fix(responses): settle a credential hop where the replay is dispatched (
Merge pull request lidge-jun#4745 from lidge-jun/codex/bl1-hop-permit…