You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
🤖 fix: interrupt stream with pending bash tool near-instantly (#478)
## Problem
When `InterruptStream` (Ctrl+C) was called during a pending bash tool
execution, the stream would hang indefinitely. This was especially
problematic for SSH workspaces where long-running commands could block
the UI for minutes.
### Root Cause
The AI SDK's `for await` loop blocks waiting for the current async
iterator operation to complete. When a bash tool is executing, the
iterator doesn't yield until `tool.execute()` returns.
Even though we call `abortController.abort()`, the bash tool's abort
listener was effectively empty:
```typescript
abortListener = () => {
if (!resolved) {
// Runtime handles the actual cancellation ← BUG: Does nothing!
// We just need to clean up our side
}
};
```
This caused:
1. `cancelStreamSafely()` calls `abortController.abort()`
2. Abort signal propagates but bash tool doesn't resolve
3. AI SDK iterator stays blocked waiting for tool to return
4. `await streamInfo.processingPromise` hangs indefinitely
5. IPC call never returns, UI frozen
For SSH workspaces, the SSH runtime's abort handler only kills the local
SSH client - the remote command keeps running, making this worse.
## Solution
Make the bash tool **actively resolve its promise** when aborted instead
of passively waiting:
```typescript
abortListener = () => {
if (!resolved) {
// Immediately resolve with abort error to unblock AI SDK stream
teardown();
resolveOnce({
success: false,
error: "Command execution was aborted",
exitCode: -2,
wall_duration_ms: Math.round(performance.now() - startTime),
});
}
};
```
This unblocks the chain:
- Tool promise resolves immediately with error
- AI SDK iterator yields
- Stream processing loop detects abort and exits
- `processingPromise` resolves
- IPC returns instantly
## Testing
Added integration test that verifies interrupt completes in < 2 seconds
even when a `sleep 60` command is running:
```typescript
test("should interrupt stream with pending bash tool call near-instantly", async () => {
// Start sleep 60
void sendMessage(workspaceId, "Run this bash command: sleep 60");
await collector.waitForEvent("tool-call-start");
// Measure interrupt time
const start = performance.now();
await interruptStream(workspaceId);
const duration = performance.now() - start;
expect(duration).toBeLessThan(2000); // Must be near-instant
});
```
_Generated with `cmux`_
0 commit comments