Conversation
…s) providers (a zombie stream was a multi-hour hang the idle timeout could not catch) BA-18's timeoutMs bounds socket INACTIVITY (req.setTimeout), which resets on any socket activity by design — so a "zombie stream" that trickles a byte forever (bytes arriving, the response never completing) never trips it. An adopter saw one generate() run 274 min and end in ECONNRESET, not a TimeoutError: the reset proves bytes were flowing, so the idle timer never fired. Nothing bounded total call duration. Add an opt-in deadlineMs on all four http(s) providers — an absolute, non-resetting wall-clock ceiling on the whole request (applyRequestDeadline in the shared src/provider-http.js). DISABLED by default (a deliberately long single call is legitimate; a default would kill it); 0/Infinity disable; per-call overridable. On trip: a TERMINAL TimeoutError distinguishable from the idle trip — code 'EDEADLINE', context.bound 'deadline', retryable:false (a hard ceiling is meant to STOP, not re-spend a full deadlineMs on retry). The idle trip now also carries context.bound 'idle' so a consumer switches on one uniform field. With both armed and timeoutMs < deadlineMs, a silent socket trips the idle bound first. Review fixes folded in: - Finding 1 (correctness): a garbage/non-numeric deadlineMs no longer silently disables the deadline (which would reintroduce the hang). resolveTimeoutMs throws a ValidationError on an explicitly-set garbage value when the knob has no safe default; the idle bound still fails safe to its 10-min default (BA-18 unchanged). - Finding 2 (cleanup): idle + deadline wiring collapsed to one applyRequestBounds seam per _request, so a future third bound is added in one place, not four. POC 5/5; +23 tests across all four providers (all four acceptance criteria + the review-fix edges); mutations proven RED incl. a deadline-OFF negative control that hangs and both review-fix mutations; full suite 1042 pass / 0 fail / 2 skip; typecheck clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6D6DPGiVG1Jo3vMbwqFPV
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why (bareloop upstream ask BA-19)
BA-18's
timeoutMsbounds socket inactivity (req.setTimeout), which resets on any socket activity by design — so a zombie stream that trickles a byte forever (bytes arriving, the response never completing) never trips it. An adopter saw onegenerate()run 274 minutes and end inECONNRESET(not aTimeoutError): the reset proves bytes were flowing, so the idle timer never fired. Nothing bounded total call duration.The fix
Opt-in
deadlineMson all four http(s) providers — an absolute, non-resetting wall-clock ceiling (applyRequestDeadlinein the sharedsrc/provider-http.js).0/Infinitydisable; per-call overridable.TimeoutErrordistinguishable from the idle trip —code:'EDEADLINE',context.bound:'deadline',retryable:false(a hard ceiling is meant to STOP, not re-spend). The idle trip now also carriescontext.bound:'idle'.timeoutMs < deadlineMs, a silent socket trips the idle bound first.Review fixes folded in
deadlineMsno longer silently disables the deadline (which would reintroduce the hang) —resolveTimeoutMsthrows aValidationErroron an explicitly-set garbage value when the knob has no safe default; the idle bound still fails safe to its 10-min default (BA-18 unchanged).applyRequestBoundsseam per_request, so a future third bound is added in one place.Verification
🤖 Generated with Claude Code