-
Notifications
You must be signed in to change notification settings - Fork 2k
Fix race condition in resolveAutoModeEndpoint causing duplicate telem… #5027
base: main
Are you sure you want to change the base?
Changes from 1 commit
f2aeea5
8c48421
f8a5355
66897f7
59875e8
e315b72
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -916,4 +916,101 @@ describe('AutomodeService', () => { | |||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| describe('concurrent resolution coalescing', () => { | ||||||||||||||||||||||||||||||||||
| it('should return the same endpoint for concurrent calls with the same conversationId', async () => { | ||||||||||||||||||||||||||||||||||
| mockApiResponse(['gpt-4o', 'gpt-4o-mini']); | ||||||||||||||||||||||||||||||||||
| automodeService = createService(); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const chatRequest: Partial<ChatRequest> = { | ||||||||||||||||||||||||||||||||||
| location: ChatLocation.Panel, | ||||||||||||||||||||||||||||||||||
| prompt: 'hello', | ||||||||||||||||||||||||||||||||||
| sessionId: 'session-concurrent' | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const [result1, result2, result3] = await Promise.all([ | ||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understand correctly, shouldn't the real test here be multiple different prompts being asked? Concurrent calls with the same conversationId only breaks our logic if they result in different answers. My confusion may stem from my lack of understanding on what the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question! The concurrent calls in the real bug all carry the same prompt and same So the test is accurate to the real scenario: same chatRequest, same sessionId, same prompt, fired concurrently. To clarify mockApiResponse(['gpt-4o', 'gpt-4o-mini']): it stubs the CAPI token endpoint to return those as available_models. It controls which models the auto-mode service can choose from — it's not the router response (that would be mockRouterResponse). Different prompts within the same conversation are already handled by the skipRouter cache (turnCount > 0 → skip), which is unrelated to this fix. This PR only addresses the race window on the first resolution of a conversation.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dug a little deeper into this one 👇 Duplicate
|
||||||||||||||||||||||||||||||||||
| Date | Hash | Author | Change |
|---|---|---|---|
| 2025-06-27 | af813ee | @kieferrm | Initial repo — multiple getChatEndpoint calls in runOne(), no auto-mode |
| 2025-10-30 | 5aafc56 | @lramos15 | Auto-mode service created, calls now async |
| 2026-01-16 | fe88d62 | @TylerLeonhardt | Model router added, calls now trigger router API |
| 2026-01-20 | 5388442 | @Copilot/@TylerLeonhardt | lastRoutedPrompt dedup — fixes cross-turn but not within-turn race |
| 2026-02-05 | 70b2b4c | @lramos15 | hasImage gate + vision fallback |
| 2026-02-19 | 8f0a331 | @nickthecook | automode.routerFallback telemetry added — made the duplication observable |
| 2026-03-25 | e8d3c5b | @lramos15 | skipRouter/turnCount, conversation-level routing finalized |
Minimal fix (this PR)
Promise coalescing in resolveAutoModeEndpoint: if a resolution is already in-flight for a conversationId, subsequent callers share the existing promise instead of starting a new one.
Proper fix (separate PR)
Resolve the endpoint once at the top of runOne() and pass it down to getAvailableTools, buildPrompt, and fetch. See abadawi/resolve-endpoint-once-per-round. This was attempted in 5388442bf (added _cachedEndpointForTurn to ToolCallingLoop) but reverted in favor of dedup inside automodeService. Now implemented as a standalone refactor.
cc @lramos15 @TylerLeonhardt — adding you both since you've touched this area extensively. Would appreciate a sanity check on these findings.
Uh oh!
There was an error while loading. Please reload this page.