Description
Bug report
After SIGINT/cancellation is delivered during a streaming tool call, Crush still produces tool-result state for the interrupted response. A shell side-effect variant did not prove a real post-cancellation side effect, so this report is limited to post-cancellation tool-result handling.
Before/after behavior
Before: the provider stream crosses a duplicate, repeated, or cancelled tool-call boundary.
After: Crush records tool-result state that should have been rejected or ignored after cancellation.
Expected: After cancellation, Crush should drop interrupted tool-call state and should not return tool results for that response.
Minimal reproducible example
Standalone reproduction
Prerequisites: Docker, Python 3, and the GitHub CLI (gh) for the clone command below. The linked reproducer is self-contained and uses only Python standard-library modules plus Docker. It builds the affected CLI version from the public upstream package/release source and starts a local mock provider; it does not require this repository or any private fuzzing harness. The Docker run is limited to 2 CPUs and 4 GiB RAM by default.
Complete self-contained reproducer: https://gist.github.com/N0zoM1z0/453828f8978e9073d6fca24e21a57d91
The Gist contains crush-tool-executes-after-cancel.reproduce.py. Download and run:
gh gist clone 453828f8978e9073d6fca24e21a57d91 crush-tool-executes-after-cancel-reproducer
cd crush-tool-executes-after-cancel-reproducer
python3 crush-tool-executes-after-cancel.reproduce.py
To reuse an already-built local image:
python3 crush-tool-executes-after-cancel.reproduce.py --skip-build
Key output from a local run against the affected version:
process_exit=-9
provider_requests=69
tool_result_counts={'edit_cancel_1': 1756}
late_stream_events_after_stop=0
tool_result edit_cancel_1: observed=1756 expected>=1
REPRODUCED
Version
v0.76.0
Environment
- Repository:
charmbracelet/crush - Version: Crush 0.76.0 - OS/arch: Linux x86_64 in Docker (node:24-bookworm) - Interface: CLI - Provider/model: OpenAI-compatible local mock provider, model gpt-4
Description
Bug report
After SIGINT/cancellation is delivered during a streaming tool call, Crush still produces tool-result state for the interrupted response. A shell side-effect variant did not prove a real post-cancellation side effect, so this report is limited to post-cancellation tool-result handling.
Before/after behavior
Before: the provider stream crosses a duplicate, repeated, or cancelled tool-call boundary.
After: Crush records tool-result state that should have been rejected or ignored after cancellation.
Expected: After cancellation, Crush should drop interrupted tool-call state and should not return tool results for that response.
Minimal reproducible example
Standalone reproduction
Prerequisites: Docker, Python 3, and the GitHub CLI (
gh) for the clone command below. The linked reproducer is self-contained and uses only Python standard-library modules plus Docker. It builds the affected CLI version from the public upstream package/release source and starts a local mock provider; it does not require this repository or any private fuzzing harness. The Docker run is limited to 2 CPUs and 4 GiB RAM by default.Complete self-contained reproducer: https://gist.github.com/N0zoM1z0/453828f8978e9073d6fca24e21a57d91
The Gist contains
crush-tool-executes-after-cancel.reproduce.py. Download and run:gh gist clone 453828f8978e9073d6fca24e21a57d91 crush-tool-executes-after-cancel-reproducer cd crush-tool-executes-after-cancel-reproducer python3 crush-tool-executes-after-cancel.reproduce.pyTo reuse an already-built local image:
Key output from a local run against the affected version:
Version
v0.76.0
Environment
charmbracelet/crush- Version:Crush 0.76.0- OS/arch: Linux x86_64 in Docker (node:24-bookworm) - Interface: CLI - Provider/model: OpenAI-compatible local mock provider, modelgpt-4