Skip to content

[Bug] Budget trimming can orphan tool results and break long sessions with HTTP 400 #291

Description

@louiseboo

Summary

Budget trimming can split an assistant tool-call message from its matching tool-result message. The next OpenAI Codex request then contains a function_call_output without its parent function_call, and the provider rejects the entire turn.

Environment

  • Observed with Raven v0.1.3
  • Confirmed by code inspection on current main
  • Provider: OpenAI Codex OAuth using the Responses API
  • Surface: a long-running gateway conversation after Curator history selection exceeds the prompt budget

Minimal reproduction

  1. Build session history containing a user message, an assistant message with tool_calls, and its matching tool-result message.
  2. Select the complete exchange through HistoryTrimmer.canonical_ids.
  3. Force HistoryTrimmer.trim over budget so the assistant message is the first non-protected item.
  4. Observe that the current loop drops only the assistant message and keeps the tool result.
  5. Send the resulting history through the OpenAI Codex provider.

Expected behavior

An assistant tool-call message and all of its corresponding tool-result messages should be treated as one structural unit during budget trimming. The entire exchange should be kept or dropped together.

Actual behavior

The parent assistant message can be dropped while the tool result remains. Structural validation is performed after the invalid history has already been built, and the provider returns:

HTTP 400: No tool call found for function call output with call_id call_REDACTED.

The gateway remains healthy and continues receiving inbound messages, but the affected conversation cannot produce a useful reply.

Root cause

HistoryTrimmer.trim closes the initial selection over tool adjacency, then removes individual message IDs inside its budget loop without preserving that closure.

Proposed fix

  • Resolve the full tool exchange for the selected message.
  • Remove the assistant call and all sibling tool results atomically.
  • Add a regression test that forces the budget loop to drop a tool exchange and verifies that no orphan result remains.

No user data, private paths, tokens, or conversation content are included in this report.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions