Duplicate tool-call ids corrupt function names and permanently kill a Gemini/Vertex session
- Linguagem predominante
- Rust
- Estrelas
- 54.2k
- Forks
- 6.2k
- Merge médio
- 3d 2h
- PRs com merge (30d)
- 262
Descrição
When Gemini reuses a tool-call id within a session, goose serialises a `functionResponse` under the wrong function name. Vertex rejects the request with `400 Request contains an invalid argument`, and because the ids are persisted the session is dead permanently — every later turn replays the same corrupt payload.
Line numbers are v1.50.0; the code is unchanged since at least v1.45.0.
### Mechanism
`crates/goose-provider-types/src/formats/google.rs:94` builds a name lookup keyed on the tool-call id, collected from **all** messages up front:
```rust
let tool_names: HashMap<_, _> = filtered.iter()
.flat_map(|message| &message.content)
.filter_map(|content| match content {
MessageContentBlock::ToolRequest(request) => request.tool_call.as_ref().ok()
.map(|tool_call| (request.id.as_str(), sanitize_function_name(&tool_call.name))),
_ => None })
.collect();
```
`:224` and `:236` resolve each response's name from it. If two requests share an id, the later overwrites the earlier, so **both** responses go out under the later call's name.
The ids are the provider's, not goose's: `:347` takes `functionCall.id` from the response and only falls back to `Uuid::new_v4()` when it is absent. `crates/goose/src/providers/formats/gcpvertexai.rs:309` routes Vertex through this parser.
### Evidence
From a session that died this way (`gcp_vertex_ai`, `gemini-3.5-flash`): `call_3335` was issued for `sugar_filter_records`, then reused 18 messages later for `sugar_get_module_fields`. Replaying that session's ten tool calls through the current logic mislabels the first response; the outgoing request then carries a `functionResponse` naming a tool that call never invoked.
Across 661 sessions and ~11,800 tool-call requests, 8 sessions carry a reused id and every one is fatal.
### Impact
The failure is disguised. goose retries at the model's known location, and the surfaced error is `error sending request for url`, so it reads as a network fault. `grep -c "Trying known location"` in the goose logs is a reliable tell.
Recovery needs a hand-edit of `sessions.db` — stop the agent, back it up, and rename the later of the two colliding pairs (the request row and its response row):
```sql
update messages set content_json = replace(content_json,'call_XXXX','call_XXXXb')
where id in (, );
```
### Suggested fix
Fill `tool_names` as the conversation is walked rather than up front, so a response resolves against the most recent *preceding* request carrying its id. That leaves provider ids untouched and is behaviour-neutral when they are unique. Patch and test below; happy to open a PR.
Guia de contribuição
Avaliação
Esta issue ainda não foi avaliada.