ephraimduncan / ephraimduncan/opencode-cursor

Composer tool loop remains busy after H2 socket closes

Open
#33 0 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.