Description
The provider timeout only covers establishing the HTTP response, not consuming the response body. fetchWithTimeout() clears its timer as soon as fetch() resolves, but the non-streaming providers then call response.json() with no remaining deadline.
A provider that sends HTTP 200 headers and then stalls without completing the JSON body can therefore leave commit-echo suggest (and model discovery during init) waiting indefinitely.
Location
src/providers/request.ts:3-24 — timeout is cleared immediately after await fetch().
src/providers/openai-compatible.ts — complete() and fetchModels() await response.json() after the timeout has been cleared.
src/providers/anthropic.ts — same pattern in complete().
src/providers/cohere.ts — same pattern in complete() and fetchModels().
Current code path
const response = await fetchWithTimeout(...);
...
const data = await response.json(); // no deadline remains here
Steps to reproduce
- Configure a provider that reaches the configured endpoint.
- Have the endpoint return a successful response with headers but keep the response body open/incomplete.
- Run a non-streaming command such as
commit-echo suggest or trigger model discovery with commit-echo init.
- Observe that the command does not time out.
This can be reproduced deterministically with a test server that writes the status/headers and then never finishes the JSON body.
Expected behavior
The configured provider timeout should bound both response acquisition and response-body consumption, or there should be a separate body-read deadline.
Actual behavior
The timeout ends at response headers. A stalled JSON body has no timeout and can hang the CLI until the user interrupts it.
Suggested fix
Keep the abort controller alive through body consumption, or provide a helper that reads response.text()/JSON under a deadline. Apply the same policy to model discovery and completion requests, not just SSE streaming.
Impact
A transient upstream/proxy failure can make normal suggest or init invocations hang indefinitely. This is separate from the existing streaming timeout work because the current streaming path has its own body-read handling.
Reviewed against current main at c67ff967a018d5fd4b032f6ef6a4981ac9d76a12.
Description
The provider timeout only covers establishing the HTTP response, not consuming the response body.
fetchWithTimeout()clears its timer as soon asfetch()resolves, but the non-streaming providers then callresponse.json()with no remaining deadline.A provider that sends HTTP 200 headers and then stalls without completing the JSON body can therefore leave
commit-echo suggest(and model discovery duringinit) waiting indefinitely.Location
src/providers/request.ts:3-24— timeout is cleared immediately afterawait fetch().src/providers/openai-compatible.ts—complete()andfetchModels()awaitresponse.json()after the timeout has been cleared.src/providers/anthropic.ts— same pattern incomplete().src/providers/cohere.ts— same pattern incomplete()andfetchModels().Current code path
Steps to reproduce
commit-echo suggestor trigger model discovery withcommit-echo init.This can be reproduced deterministically with a test server that writes the status/headers and then never finishes the JSON body.
Expected behavior
The configured provider timeout should bound both response acquisition and response-body consumption, or there should be a separate body-read deadline.
Actual behavior
The timeout ends at response headers. A stalled JSON body has no timeout and can hang the CLI until the user interrupts it.
Suggested fix
Keep the abort controller alive through body consumption, or provide a helper that reads
response.text()/JSON under a deadline. Apply the same policy to model discovery and completion requests, not just SSE streaming.Impact
A transient upstream/proxy failure can make normal
suggestorinitinvocations hang indefinitely. This is separate from the existing streaming timeout work because the current streaming path has its own body-read handling.Reviewed against current
mainatc67ff967a018d5fd4b032f6ef6a4981ac9d76a12.