ephraimduncan / ephraimduncan/opencode-cursor
Composer tool loop remains busy after H2 socket closes
- Dominant language
- TypeScript
- Stars
- 276
- Forks
- 31
- Avg merge
- 4h 13m
- Merged PRs (30d)
- 1
Description
## Environment
- OpenCode: `1.18.9`
- Plugin: `opencode-cursor-oauth 0.1.1`
- OS: macOS
- Node.js: `22.19.0`
- Bun: `1.3.14`
## Summary
Composer can enter a busy state after a multi-turn tool loop when the H2 socket closes. The run does not emit a normal finish event, does not exit the tool loop, and does not return to idle. The same pressure test passes with Grok under the same client and tool setup.
Observed error text:
```text
Cannot connect to API: The socket connection was closed unexpectedly
```
## Sanitized timeline
- `11:02:12Z`: last stream activity
- `11:02:40Z`: H2 socket closed
- `11:06:28Z`: later retry
- `11:10:48Z`: later retry
The timestamps are approximate and intentionally omit local identifiers and request details.
## Expected behavior
When the H2 stream closes, the proxy should deterministically emit a terminal error or completion, close the associated stream state, and let the client exit the tool loop. A retry should either establish a clean new stream or fail with a bounded, visible error. The Composer run should never remain indefinitely busy without a finish/exiting-loop/idle transition.
## Safe reproduction outline
1. Configure OpenCode with the Cursor OAuth plugin and a Composer model.
2. Run a read-only agent task that requires several sequential tool turns, such as inspecting files, searching for a symbol, and returning a final summary.
3. Allow the task to continue through multiple tool-result round trips.
4. Observe a stream/socket close during the loop.
5. Observe that Composer remains busy instead of emitting a terminal event or returning to idle.
6. Repeat the same multi-round read-only test with Grok; it completes successfully under the same pressure conditions.
No credentials, private payloads, session identifiers, or full prompts are included.
## Network observations
At the time of investigation, basic unauthenticated DNS, TCP 443, TLS, HTTPS, and HTTP/2 connectivity to the Cursor host was normal, including repeated HTTP/2 root requests. There were also intermittent historical connection anomalies, including timeout, connection reset, DNS resolution, and TLS handshake errors. This suggests the proxy should handle transient H2 failures explicitly, but the issue is not explained by a persistent basic connectivity outage.
## Suspected code areas
- `src/proxy.ts`: clean end-stream and `onClose` branches around lines 1493-1542
- `src/h2-bridge.mjs`: stderr and error handling for the child H2 bridge
Please verify that socket-close paths settle pending promises, clear stream/session state, and propagate a terminal event to the OpenAI-compatible response layer.
## Validation suggestion
Add a replay or integration test that runs multiple Composer tool/result turns, injects an H2 socket close after stream activity, and asserts that the request reaches exactly one terminal state: completed, failed, or bounded retry. Also assert that no pending tool loop or busy state remains after the close.
Contributor guide
No contributing guide indexed for this repository
Research direction
Read the end-stream and onClose branches in src/proxy.ts around lines 1493-1542, then trace the stderr and error handling in src/h2-bridge.mjs. Add a replay or integration test for multiple Composer tool/result turns with an injected H2 socket close; done means exactly one terminal state and no pending tool loop or busy state.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100