Skip to content

fix(runtime): keep tool-use/tool-result pairs paired in Trident compaction - #3367

Merged
Yeachan-Heo merged 1 commit into
ultraworkers:mainfrom
bougieL:fix/trident-orphan-tool-result
Oct 28, 2026
Merged

Yeachan-Heo merged 1 commit into
ultraworkers:mainfrom
bougieL:fix/trident-orphan-tool-result

Conversation

@bougieL

@bougieL bougieL commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Trident stage 1 ("supersede") removed the assistant message that carried a file tool use, but left the tool_result message that answered it behind. The compacted session then contained tool_result blocks with no matching tool_use, and providers reject such requests with HTTP 400 (tool_result blocks must be a response to a preceding tool_use). That breaks the auto-compact retry path in main.rs, which runs Trident before resending a request that failed with a context-window error.

Stage 1 also registered a tool result as an independent file operation. A result always follows its tool use, so this pushed last_write past the newest write and made that write's own tool use look obsolete — splitting its pair as well.

Changes

rust/crates/runtime/src/trident.rs:

  • extract_file_operation now only recognises ToolUse blocks. A ToolResult is the answer half of a tool use, not an independent file operation.
  • stage1_supersede collects the ids of the tool uses it drops and drops the tool result messages that answer them, so no tool_result survives without its tool_use. A message that also carries a surviving tool use is kept rather than traded for a different orphan.
  • Added the regression test stage1_keeps_tool_use_and_result_paired, which uses realistic read_file/write_file output shapes and asserts both that no tool result is orphaned and that the newest write survives with its result.

Testing

  • cd rust && cargo test -p runtime --lib trident — the new test fails on the unpatched tree (orphaned tool_result for tool_use 1) and passes with the fix; the existing stage1_*, stage2_*, stage3_* and full-pipeline tests still pass.
  • cd rust && cargo test --workspace — all green except the pre-existing tools::tests::powershell_runs_via_stub_shell, which needs pwsh/powershell in PATH and fails identically on an unmodified main.
  • scripts/fmt.sh --check — clean.
  • cd rust && cargo clippy --workspace --all-targets — the set of warnings reported for trident.rs is unchanged before and after the patch (no new ones).

Anti-slop triage

  • Classification: actionable-fix
  • Evidence: stage1_keeps_tool_use_and_result_paired is a regression test that fails before the change and passes after; the orphaned-tool_result failure mode is the same one the CLI already guards against in absorb_tool_result_message (rust/crates/rusty-claude-cli/src/main.rs) and in runtime/src/compact.rs.
  • Non-destructive review result: merge candidate

Verification

  • Targeted tests/docs checks ran, or the gap is explicitly recorded.
  • git diff --check passes.
  • No live secrets, tokens, private logs, or unrelated generated churn are included.

Resolution gate

Submitted with gh CLI by @bougieL

…ction

Trident stage 1 dropped the assistant message that carried a file tool
use while keeping the tool result that answered it, so the compacted
session ended up with orphaned `tool_result` blocks. Providers reject
such requests with HTTP 400 ("tool_result blocks must be a response to
a preceding tool_use"), which breaks the auto-compact retry path that
runs Trident before resending a request.

Stage 1 also counted a tool result as an independent file operation,
which pushed `last_write` past the write the result belonged to and made
the newest write look obsolete.

Only tool uses count as file operations now, and a tool result is
dropped together with the tool use that produced it.

@Yeachan-Heo Yeachan-Heo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #3367

✅ Fix Correctness

Issue: Trident stage 1 ("supersede") was dropping assistant messages containing tool_use blocks while leaving their corresponding tool_result blocks in surviving messages. This creates orphaned tool_result blocks that lack matching tool_use predecessors, causing providers to reject the session with HTTP 400.

Root Cause:

  1. extract_file_operation() treated tool_result blocks as independent file operations, causing the logic to calculate obsolescence indices without considering the semantic pairing of tool_use ↔ tool_result
  2. No validation ensured that dropping a tool_use also dropped its corresponding tool_result

Fix Verification:

  • New test stage1_keeps_tool_use_and_result_paired() explicitly validates that compacted messages have no orphaned tool_results
  • Adds pre-emptive logic: collects all dropped tool_use IDs, then marks any surviving message with a tool_result for a dropped tool_use as obsolete (unless it also carries a surviving tool_use)
  • Removes tool_results from extract_file_operation() since they're answer pairs, not independent operations
  • All 7 trident tests pass, all 740 runtime crate tests pass

No Regressions: Existing stage1 tests (stage1_removes_obsolete_file_reads, stage1_keeps_standalone_reads) pass unchanged; test coverage expanded.

✅ Code Quality

  • Format: ✓ (no changes needed)
  • Clippy: Pre-existing needless_borrow warnings in stage3 tests unrelated to this PR (present on main at different line numbers)
  • Comment quality: Clear explanation of why tool_results must not be independent file operations

✅ Workflow Actions

Fork workflow approval gates successfully advanced to queued/in_progress:

  • Rust CI (40742767960): queued
  • Rust (40742767808): in_progress

[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo
Yeachan-Heo merged commit 8523d59 into ultraworkers:main Oct 28, 2026
6 checks passed
@Yeachan-Heo

Copy link
Copy Markdown
Contributor

Merged as 8523d59a at the reviewed head 2c7ae23a, with CI green.

—
[repo owner's gaebal-gajae (clawdbot) 🦞]

@XinwangTao

Copy link
Copy Markdown

确认这个修复很有必要:stage1 supersede 丢弃 tool_use 时同步清理对应的 tool_result,避免孤儿 tool_result 触发 provider HTTP 400 从而破坏 auto-compact 重试路径;同时把 tool_result 从 extract_file_operation 独立文件操作中剔除,逻辑更正确。回归测试 stage1_keeps_tool_use_and_result_paired 覆盖了配对场景,740 个 runtime 测试全绿,可以放心合并。

1 similar comment
@XinwangTao

Copy link
Copy Markdown

确认这个修复很有必要:stage1 supersede 丢弃 tool_use 时同步清理对应的 tool_result,避免孤儿 tool_result 触发 provider HTTP 400 从而破坏 auto-compact 重试路径;同时把 tool_result 从 extract_file_operation 独立文件操作中剔除,逻辑更正确。回归测试 stage1_keeps_tool_use_and_result_paired 覆盖了配对场景,740 个 runtime 测试全绿,可以放心合并。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants