Commit cb523ed
authored
🤖 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`_1 parent 8b6d39a commit cb523ed
2 files changed
+71
-5
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
112 | 112 | | |
113 | 113 | | |
114 | 114 | | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
115 | 119 | | |
116 | 120 | | |
117 | 121 | | |
| |||
124 | 128 | | |
125 | 129 | | |
126 | 130 | | |
127 | | - | |
| 131 | + | |
128 | 132 | | |
129 | 133 | | |
130 | 134 | | |
131 | 135 | | |
132 | | - | |
133 | | - | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
134 | 145 | | |
135 | 146 | | |
136 | 147 | | |
| |||
163 | 174 | | |
164 | 175 | | |
165 | 176 | | |
166 | | - | |
167 | | - | |
| 177 | + | |
| 178 | + | |
168 | 179 | | |
169 | 180 | | |
170 | 181 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
133 | 133 | | |
134 | 134 | | |
135 | 135 | | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
136 | 191 | | |
137 | 192 | | |
138 | 193 | | |
| |||
0 commit comments