Long coding-agent histories can reuse a tool call ID for distinct chronological operations. We needed a local normalization workaround for this on the Cloud Code path; the current translator still forwards those IDs unchanged.
The source inspection below is against release/v3.8.52 at 4399e60057aaee564ec9e31054a5f6c76cc92a2d. The earlier live incident used the published 3.8.51 Docker runtime. These are separate observations, not a claim that we reproduced the provider rejection on a fresh 3.8.52 deployment.
Reproduction from the repository root, using the existing TypeScript loader:
node --import tsx/esm --import ./tests/_setup/isolateDataDir.ts --input-type=module <<'JS'
import { openaiResponsesToOpenAIRequest } from './open-sse/translator/request/openai-responses.ts';
import { openaiToCloudCodeGeminiRequest } from './open-sse/translator/request/openai-to-gemini.ts';
const body = {
input: [
{ type: 'message', role: 'user', content: 'First turn' },
{ type: 'function_call', call_id: 'call_repeat', name: 'first_tool', arguments: '{}' },
{ type: 'function_call_output', call_id: 'call_repeat', output: 'FIRST_RESULT' },
{ type: 'message', role: 'user', content: 'Second turn' },
{ type: 'function_call', call_id: 'call_repeat', name: 'second_tool', arguments: '{}' },
{ type: 'function_call_output', call_id: 'call_repeat', output: 'SECOND_RESULT' },
{ type: 'message', role: 'user', content: 'Continue' },
],
};
const chat = openaiResponsesToOpenAIRequest('gemini-3.8-flash', body, false, null);
const wire = openaiToCloudCodeGeminiRequest('gemini-3.8-flash', chat, false);
const parts = wire.contents.flatMap(content => content.parts);
console.log(JSON.stringify(parts.filter(part => part.functionCall || part.functionResponse), null, 2));
JS
Both native functionCall.id values, and their functionResponse.id values, are call_repeat. The current turn-local maps do retain the correct function names and result content; this is not the older cross-turn name/content overwrite bug.
In the live incident, full histories containing reused IDs produced an upstream HTTP 400 INVALID_ARGUMENT and a downstream stream that ended without response.completed. Short prompts with the same tool definitions worked. Removing reasoning/custom-tool entries did not help. Keeping the complete histories and renaming only subsequent repeated IDs plus their corresponding outputs did: two histories with 601 and 1,534 items subsequently completed. These were diagnostic replays, not byte-identical captures of the desktop request.
The local repair retains the first ID, reserves IDs appearing later in the input, assigns unused suffixes to subsequent chronological calls, preserves the original body, and is idempotent. Unit tests and the synthetic translator reproduction confirm the normalized Cloud Code call/result pairs use call_repeat and call_repeat_occ2.
I would not port that route-level workaround verbatim:
- Scope any normalization to the translated non-native history, not native Responses passthrough or the client's stored history.
- Preserve thought-signature lookup using the original identities before assigning new wire IDs.
- Handle function and custom-tool pairs, long IDs, future-ID collisions, and repeated normalization.
- Two overlapping calls with the same ID are ambiguous. The local "latest preceding call" heuristic attaches both following outputs to the second call; that case needs explicit validation rather than guessed pairing.
Would a translator-boundary normalization for unambiguous chronological reuse be welcome? I can send the source change and focused regressions separately from the runtime bundle patch.
Long coding-agent histories can reuse a tool call ID for distinct chronological operations. We needed a local normalization workaround for this on the Cloud Code path; the current translator still forwards those IDs unchanged.
The source inspection below is against
release/v3.8.52at4399e60057aaee564ec9e31054a5f6c76cc92a2d. The earlier live incident used the published 3.8.51 Docker runtime. These are separate observations, not a claim that we reproduced the provider rejection on a fresh 3.8.52 deployment.Reproduction from the repository root, using the existing TypeScript loader:
Both native
functionCall.idvalues, and theirfunctionResponse.idvalues, arecall_repeat. The current turn-local maps do retain the correct function names and result content; this is not the older cross-turn name/content overwrite bug.In the live incident, full histories containing reused IDs produced an upstream HTTP 400
INVALID_ARGUMENTand a downstream stream that ended withoutresponse.completed. Short prompts with the same tool definitions worked. Removing reasoning/custom-tool entries did not help. Keeping the complete histories and renaming only subsequent repeated IDs plus their corresponding outputs did: two histories with 601 and 1,534 items subsequently completed. These were diagnostic replays, not byte-identical captures of the desktop request.The local repair retains the first ID, reserves IDs appearing later in the input, assigns unused suffixes to subsequent chronological calls, preserves the original body, and is idempotent. Unit tests and the synthetic translator reproduction confirm the normalized Cloud Code call/result pairs use
call_repeatandcall_repeat_occ2.I would not port that route-level workaround verbatim:
Would a translator-boundary normalization for unambiguous chronological reuse be welcome? I can send the source change and focused regressions separately from the runtime bundle patch.