fix(proxy): quarantine silent HTTP bridge sessions - #1405
Conversation
An accepted response.create can remain silent before response.created, leaving the bridge gate occupied and later client requests unable to make progress. Bound that ambiguous wait, retire the bridge, and let only the next independent retry use direct HTTP. Constraint: Upstream acceptance is ambiguous after response.create is sent. Rejected: Replay the same request | risks duplicates and forked continuity Confidence: high Scope-risk: moderate Directive: Keep same-call no-replay and previous-response owner pinning. Tested: 146 bridge/API tests; Ruff; ty; strict OpenSpec validation. Not-tested: Full cross-platform CI and upstream cloud review. Issue: Soju06#1404
|
To keep the silent-upstream family coherent we're consolidating on the #1410 watchdog track first (rationale in the #1394 discussion). The quarantine idea can still complement it — a per-key HTTP-fallback window is orthogonal to watchdog retirement — so keeping this open as a follow-up candidate rather than a competing root fix. Two functional concerns from the current diff (
Also note CI is red on this branch's own account, not just the known architecture ratchet: |
|
This pull request has been automatically marked as stale because it has had no activity for 7 days. It will be closed in 23 more days unless there is new activity. If this is still relevant, please:
Thanks for the contribution 🙏 |
|
Maintainer decision: quarantine (this PR's direction) is the chosen approach for silent-bridge sessions over the account-health penalty alternative (#1574, now closed). To proceed it needs: (1) rebase across #1562/#1394 (the reconnect/recovery surface moved substantially), (2) the 07-24 findings addressed (quarantine never trips on the reattached shape from #1534; the 5s response.created timeout is too aggressive post-#1394 — align with the 60s eventless deadline). If you don't have bandwidth, say so and we can pick it up. |
|
@WangErgouaaaa checking in — quarantine remains the chosen approach for silent HTTP-bridge sessions (maintainer decision above, #1574 closed in its favor), so this PR is wanted; it needs a revival pass to proceed. Concrete checklist:
If you don't have bandwidth, just say so and we'll take it over with attribution. Absent a reply or a push within a week, we'll assume the latter so the chosen direction doesn't stale out. |
A bridge session that has proven silent/wedged must stop attracting new attach attempts. Two shapes prove it: a reattached stream (proxy-injected previous_response_id) that delivers upstream response events but never gets response.created assigned (the #1534 production wedge, which the response_event_count == 0 gates in the eventless watchdog and the fenced durable-anchor clear never trip on), and a session key that hits two consecutive eventless missing_response_created_timeout retires. Quarantine is bounded, in-memory, session-scoped, and account-neutral: - Excluded from re-attach/session-reuse selection so later requests take a fresh session. - Fresh-reattach durable-anchor injection is skipped for full-resend payloads while the key is quarantined; delta-only payloads keep the anchor (same boundary as the fenced anchor clear). - Cleared on a completed response for the key, a 600s TTL, and a registry size cap; no durable rows, no account-health writes, no new settings. Triggers are only ever evaluated when a request is already being failed or its session retired — never against a live owned turn — so deferred- reasoning streams with long legitimate event gaps can never be quarantined. First-party takeover of #1405 (quarantine direction chosen over the #1574 account-health alternative), rebuilt on the merged #1394/#1563/#1600 recovery machinery with the 07-24 review findings addressed. Co-authored-by: WangErgouaaaa <117421439+WangErgouaaaa@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A bridge session that has proven silent/wedged must stop attracting new attach attempts. Two shapes prove it: a reattached stream (proxy-injected previous_response_id) that delivers upstream response events but never gets response.created assigned (the #1534 production wedge, which the response_event_count == 0 gates in the eventless watchdog and the fenced durable-anchor clear never trip on), and a session key that hits two consecutive eventless missing_response_created_timeout retires. Quarantine is bounded, in-memory, session-scoped, and account-neutral: - Excluded from re-attach/session-reuse selection so later requests take a fresh session. - Fresh-reattach durable-anchor injection is skipped for full-resend payloads while the key is quarantined; delta-only payloads keep the anchor (same boundary as the fenced anchor clear). - Cleared on a completed response for the key, a 600s TTL, and a registry size cap; no durable rows, no account-health writes, no new settings. Triggers are only ever evaluated when a request is already being failed or its session retired — never against a live owned turn — so deferred- reasoning streams with long legitimate event gaps can never be quarantined. First-party takeover of #1405 (quarantine direction chosen over the #1574 account-health alternative), rebuilt on the merged #1394/#1563/#1600 recovery machinery with the 07-24 review findings addressed. Co-authored-by: WangErgouaaaa <117421439+WangErgouaaaa@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A bridge session that has proven silent/wedged must stop attracting new attach attempts. Two shapes prove it: a reattached stream (proxy-injected previous_response_id) that delivers upstream response events but never gets response.created assigned (the #1534 production wedge, which the response_event_count == 0 gates in the eventless watchdog and the fenced durable-anchor clear never trip on), and a session key that hits two consecutive eventless missing_response_created_timeout retires. Quarantine is bounded, in-memory, session-scoped, and account-neutral: - Excluded from re-attach/session-reuse selection so later requests take a fresh session. - Fresh-reattach durable-anchor injection is skipped for full-resend payloads while the key is quarantined; delta-only payloads keep the anchor (same boundary as the fenced anchor clear). - Cleared on a completed response for the key, a 600s TTL, and a registry size cap; no durable rows, no account-health writes, no new settings. Triggers are only ever evaluated when a request is already being failed or its session retired — never against a live owned turn — so deferred- reasoning streams with long legitimate event gaps can never be quarantined. First-party takeover of #1405 (quarantine direction chosen over the #1574 account-health alternative), rebuilt on the merged #1394/#1563/#1600 recovery machinery with the 07-24 review findings addressed. Co-authored-by: WangErgouaaaa <117421439+WangErgouaaaa@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A bridge session that has proven silent/wedged must stop attracting new attach attempts. Two shapes prove it: a reattached stream (proxy-injected previous_response_id) that delivers upstream response events but never gets response.created assigned (the #1534 production wedge, which the response_event_count == 0 gates in the eventless watchdog and the fenced durable-anchor clear never trip on), and a session key that hits two consecutive eventless missing_response_created_timeout retires. Quarantine is bounded, in-memory, session-scoped, and account-neutral: - Excluded from re-attach/session-reuse selection so later requests take a fresh session. - Fresh-reattach durable-anchor injection is skipped for full-resend payloads while the key is quarantined; delta-only payloads keep the anchor (same boundary as the fenced anchor clear). - Cleared on a completed response for the key, a 600s TTL, and a registry size cap; no durable rows, no account-health writes, no new settings. Triggers are only ever evaluated when a request is already being failed or its session retired — never against a live owned turn — so deferred- reasoning streams with long legitimate event gaps can never be quarantined. First-party takeover of #1405 (quarantine direction chosen over the #1574 account-health alternative), rebuilt on the merged #1394/#1563/#1600 recovery machinery with the 07-24 review findings addressed. Co-authored-by: WangErgouaaaa <117421439+WangErgouaaaa@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A bridge session that has proven silent/wedged must stop attracting new attach attempts. Two shapes prove it: a reattached stream (proxy-injected previous_response_id) that delivers upstream response events but never gets response.created assigned (the #1534 production wedge, which the response_event_count == 0 gates in the eventless watchdog and the fenced durable-anchor clear never trip on), and a session key that hits two consecutive eventless missing_response_created_timeout retires. Quarantine is bounded, in-memory, session-scoped, and account-neutral: - Excluded from re-attach/session-reuse selection so later requests take a fresh session. - Fresh-reattach durable-anchor injection is skipped for full-resend payloads while the key is quarantined; delta-only payloads keep the anchor (same boundary as the fenced anchor clear). - Cleared on a completed response for the key, a 600s TTL, and a registry size cap; no durable rows, no account-health writes, no new settings. Triggers are only ever evaluated when a request is already being failed or its session retired — never against a live owned turn — so deferred- reasoning streams with long legitimate event gaps can never be quarantined. First-party takeover of #1405 (quarantine direction chosen over the #1574 account-health alternative), rebuilt on the merged #1394/#1563/#1600 recovery machinery with the 07-24 review findings addressed. Co-authored-by: WangErgouaaaa <117421439+WangErgouaaaa@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A bridge session that has proven silent/wedged must stop attracting new attach attempts. Two shapes prove it: a reattached stream (proxy-injected previous_response_id) that delivers upstream response events but never gets response.created assigned (the #1534 production wedge, which the response_event_count == 0 gates in the eventless watchdog and the fenced durable-anchor clear never trip on), and a session key that hits two consecutive eventless missing_response_created_timeout retires. Quarantine is bounded, in-memory, session-scoped, and account-neutral: - Excluded from re-attach/session-reuse selection so later requests take a fresh session. - Fresh-reattach durable-anchor injection is skipped for full-resend payloads while the key is quarantined; delta-only payloads keep the anchor (same boundary as the fenced anchor clear). - Cleared on a completed response for the key, a 600s TTL, and a registry size cap; no durable rows, no account-health writes, no new settings. Triggers are only ever evaluated when a request is already being failed or its session retired — never against a live owned turn — so deferred- reasoning streams with long legitimate event gaps can never be quarantined. First-party takeover of #1405 (quarantine direction chosen over the #1574 account-health alternative), rebuilt on the merged #1394/#1563/#1600 recovery machinery with the 07-24 review findings addressed. Co-authored-by: WangErgouaaaa <117421439+WangErgouaaaa@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A bridge session that has proven silent/wedged must stop attracting new attach attempts. Two shapes prove it: a reattached stream (proxy-injected previous_response_id) that delivers upstream response events but never gets response.created assigned (the #1534 production wedge, which the response_event_count == 0 gates in the eventless watchdog and the fenced durable-anchor clear never trip on), and a session key that hits two consecutive eventless missing_response_created_timeout retires. Quarantine is bounded, in-memory, session-scoped, and account-neutral: - Excluded from re-attach/session-reuse selection so later requests take a fresh session. - Fresh-reattach durable-anchor injection is skipped for full-resend payloads while the key is quarantined; delta-only payloads keep the anchor (same boundary as the fenced anchor clear). - Cleared on a completed response for the key, a 600s TTL, and a registry size cap; no durable rows, no account-health writes, no new settings. Triggers are only ever evaluated when a request is already being failed or its session retired — never against a live owned turn — so deferred- reasoning streams with long legitimate event gaps can never be quarantined. First-party takeover of #1405 (quarantine direction chosen over the #1574 account-health alternative), rebuilt on the merged #1394/#1563/#1600 recovery machinery with the 07-24 review findings addressed. Co-authored-by: WangErgouaaaa <117421439+WangErgouaaaa@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A bridge session that has proven silent/wedged must stop attracting new attach attempts. Two shapes prove it: a reattached stream (proxy-injected previous_response_id) that delivers upstream response events but never gets response.created assigned (the #1534 production wedge, which the response_event_count == 0 gates in the eventless watchdog and the fenced durable-anchor clear never trip on), and a session key that hits two consecutive eventless missing_response_created_timeout retires. Quarantine is bounded, in-memory, session-scoped, and account-neutral: - Excluded from re-attach/session-reuse selection so later requests take a fresh session. - Fresh-reattach durable-anchor injection is skipped for full-resend payloads while the key is quarantined; delta-only payloads keep the anchor (same boundary as the fenced anchor clear). - Cleared on a completed response for the key, a 600s TTL, and a registry size cap; no durable rows, no account-health writes, no new settings. Triggers are only ever evaluated when a request is already being failed or its session retired — never against a live owned turn — so deferred- reasoning streams with long legitimate event gaps can never be quarantined. First-party takeover of #1405 (quarantine direction chosen over the #1574 account-health alternative), rebuilt on the merged #1394/#1563/#1600 recovery machinery with the 07-24 review findings addressed. Co-authored-by: WangErgouaaaa <117421439+WangErgouaaaa@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A bridge session that has proven silent/wedged must stop attracting new attach attempts. Two shapes prove it: a reattached stream (proxy-injected previous_response_id) that delivers upstream response events but never gets response.created assigned (the #1534 production wedge, which the response_event_count == 0 gates in the eventless watchdog and the fenced durable-anchor clear never trip on), and a session key that hits two consecutive eventless missing_response_created_timeout retires. Quarantine is bounded, in-memory, session-scoped, and account-neutral: - Excluded from re-attach/session-reuse selection so later requests take a fresh session. - Fresh-reattach durable-anchor injection is skipped for full-resend payloads while the key is quarantined; delta-only payloads keep the anchor (same boundary as the fenced anchor clear). - Cleared on a completed response for the key, a 600s TTL, and a registry size cap; no durable rows, no account-health writes, no new settings. Triggers are only ever evaluated when a request is already being failed or its session retired — never against a live owned turn — so deferred- reasoning streams with long legitimate event gaps can never be quarantined. First-party takeover of #1405 (quarantine direction chosen over the #1574 account-health alternative), rebuilt on the merged #1394/#1563/#1600 recovery machinery with the 07-24 review findings addressed. Co-authored-by: WangErgouaaaa <117421439+WangErgouaaaa@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#1630) * fix(proxy): quarantine silent HTTP bridge sessions A bridge session that has proven silent/wedged must stop attracting new attach attempts. Two shapes prove it: a reattached stream (proxy-injected previous_response_id) that delivers upstream response events but never gets response.created assigned (the #1534 production wedge, which the response_event_count == 0 gates in the eventless watchdog and the fenced durable-anchor clear never trip on), and a session key that hits two consecutive eventless missing_response_created_timeout retires. Quarantine is bounded, in-memory, session-scoped, and account-neutral: - Excluded from re-attach/session-reuse selection so later requests take a fresh session. - Fresh-reattach durable-anchor injection is skipped for full-resend payloads while the key is quarantined; delta-only payloads keep the anchor (same boundary as the fenced anchor clear). - Cleared on a completed response for the key, a 600s TTL, and a registry size cap; no durable rows, no account-health writes, no new settings. Triggers are only ever evaluated when a request is already being failed or its session retired — never against a live owned turn — so deferred- reasoning streams with long legitimate event gaps can never be quarantined. First-party takeover of #1405 (quarantine direction chosen over the #1574 account-health alternative), rebuilt on the merged #1394/#1563/#1600 recovery machinery with the 07-24 review findings addressed. Co-authored-by: WangErgouaaaa <117421439+WangErgouaaaa@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(proxy): reset session quarantine flag on clear and expire stale eventless strikes Two hardening fixes from adversarial review: a surviving session becomes reusable again once a completed response disproves the wedge (the session flag now resets alongside the registry clear), and a TTL-expired first eventless strike can no longer be resurrected into a "consecutive" second strike (prune runs before the increment). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(proxy): close quarantine bypass paths flagged by review Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: WangErgouaaaa <117421439+WangErgouaaaa@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
Superseded by #1630, which landed the quarantine direction first-party today (owner decision after no response to the 07-24 review, the 08-04 takeover offer, and the 08-06 revival checklist). Your quarantine-window concept, per-key deadline registry with pruning and size-cap eviction, and event wording were carried forward with attribution — the merge commit credits you via Co-authored-by. Thanks for pioneering the approach; the account-health-neutral session-scoped shape you argued for is exactly what shipped. |
Summary
Bound accepted-but-silent HTTP bridge submissions so one stuck upstream
websocket cannot hold a session gate and stall later Codex requests. The
ambiguous current call fails without an internal replay; the next independent
retry temporarily bypasses that bridge over direct HTTP while preserving
previous_response_idaccount ownership.Fixes #1404
Type of change
fix:— bug fix (no behavior change beyond the bug)feat:— new user-facing feature or capabilityrefactor:— internal refactor (no behavior change, no API change)docs:— documentation onlychore:/ci:/build:— tooling, CI, packagingtest:— test-only changeLinked issue: Fixes #1404
OpenSpec
upstream-equivalent request/SSE behavior
Change directory:
openspec/changes/quarantine-silent-http-bridge-sessions/Changes
response.createis sent and bound the wait forresponse.created(default 5 seconds).bridge, and quarantine its affinity key for a bounded interval (default 60
seconds).
resolution keeps
previous_response_idchains on the account that createdthem.
downstream stream closes, including after only the initial SSE heartbeat.
unrelated-account concurrency, and disconnect cleanup.
Safety invariant
Once
response.createhas been sent, upstream acceptance is ambiguous. This PRdeliberately does not replay the same request inside that client call;
automatic replay could duplicate work or fork a response chain.
Simplicity
.env.exampleentry, dashboard nav item, or UI changehttp_responses_session_bridge_response_created_timeout_seconds=5:conservative working default; override remains available only for incident
tuning on unusually slow upstreams.
http_responses_session_bridge_quarantine_seconds=60: conservativerecovery window; override remains available only for incident tuning and
can be set to zero to disable quarantine.
Test plan
The full
make ciparity target was not run on this Windows host because GNUmake, Docker/Helm, and local PostgreSQL gates are unavailable; GitHub CI
remains required. The standalone architecture script reaches a pre-existing
origin/mainratchet failure (load_balancer.py: 3260 lines vs 3021) in anuntouched file. Touched ratcheted files remain within limits (
service.py:2600, HTTP bridge mixin: 2393, streaming mixin: 1100).
Screenshots / output
No dashboard-visible change. Expected recovery sequence is observable as
low-cardinality bridge events:
Checklist
path.
uv run pre-commit run local-ci --hook-stage manual --all-files(unavailable on this host; relevant local gates are listed above).
CHANGELOG.mdwas not edited.