Skip to content

[PR] fix: first-byte stall warn per (origin,session) with cooldown instead of once-per-origin-per-process (#5164) - #5260

Open
ranxianglei wants to merge 4 commits into
masterfrom
2026-10-31_first-byte-warn-cooldown
Open

ranxianglei wants to merge 4 commits into
masterfrom
2026-10-31_first-byte-warn-cooldown

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Related to #5164 (stopgap / option-B content). Direction + default threshold remain an owner decision in #5262 — if A lands, this PR is its diagnostic foundation (acceptance ②④); if C, reject this PR.

What changed

The first-byte watchdog (armFirstByteWatchdog, src/server/relay.ts) deduped warns with a process-lifetime Set per origin: each origin could ever warn once, claimed at fire time. In #5139 Phase 2 that meant the first post-wedge late fire burned ccproxy's single quota and every later stalled turn of the mega-session went completely silent — zero log lines explaining why turns were held ~300s.

Warns are now throttled per (origin, session) with a cooldown (FIRST_BYTE_WARN_COOLDOWN_MS = 60_000, code constant + test seam, no new config surface):

Behavior change disclosure (old → new)

First-byte stall warn cadence: once per origin per process lifetime → at most once per (origin, session) per 60s. Log volume can increase for origins with repeated stalls (bounded by the cooldown). Client-visible behavior UNCHANGED — diagnostic only: no abort, no new failure path, no config surface (the fail-fast variant stays owner-gated in #5262).

Gate tests (#3617 both directions)

  • stall < threshold → no warn, no abort (kept);
  • stall > threshold → warn with session/lane/target + actual pending seconds (kept + extended);
  • same origin+session, stalls beyond cooldown apart → second warn fires (new positive — regression pin for quota burnout, acceptance ②④);
  • same origin+session within cooldown → suppressed (new negative — anti-spam);
  • different sessions, same origin → both warn (new positive; old code disarmed the second at arm time);
  • different origins → both warn (kept); client-abort / pre-aborted signal / disabled threshold → no warn (kept); state-reset seam re-enabled (renamed _resetFirstByteWarnStateForTest).

Acceptance ③ (healthy long streams crossing the threshold survive) is untouched: this change touches neither fetchWithTimeout's idle re-arm nor any response path — covered by the existing fetch-util/relay suites.

Modes reasoned

Plugin mode and proxy mode both funnel through the single forward chokepoint (the watchdog arms around the initial upstream fetch at relay.ts regardless of lane — lane ∈ main/side/passthrough); the change is lane-agnostic logging only.

kernel: none · pi-subagents: none · config surface: none (constant + test seam, matching how the existing threshold is treated)

@ranxianglei ranxianglei changed the title fix: first-byte stall warn per (origin,session) with cooldown instead of once-per-origin-per-process (#5164) [PR] fix: first-byte stall warn per (origin,session) with cooldown instead of once-per-origin-per-process (#5164) Oct 30, 2026
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-31_first-byte-warn-cooldown (604f38a)

Latest build — install this exact version

npm install -g billion-context@0.0.7309-pr.5260.842ebc4

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Recent builds (commit → version)

commit version build
604f38a billion-context@0.0.7309-pr.5260.842ebc4 build
b8a4475 billion-context@0.0.7252-pr.5260.df71d5c build
414e291 billion-context@0.0.7204-pr.5260.74ac75b build

Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr5260.tgz
npm install -g package

Install instructions are refreshed on each push.

ranxianglei pushed a commit that referenced this pull request Oct 31, 2026
Review finding: both learn sites nested the veto clear inside the learned-flag flip guard (relay demand branch / loop UpstreamHttpError else-if). A session holding BOTH evidence classes — learned ON from an earlier demand 400, later vetoed by an extra-forbidden 4xx — could never clear its veto: re-demand evidence arriving while the flag was already true did nothing, so every tool-call turn 400'd forever in the opposite direction (the mirror image of #5260's stuck loop). The veto clear is now an independent transition in src/server/relay.ts and src/loop/core.ts, with its own warn log and markDirty, so the two evidence classes keep flipping the session either way. Regression test drives the full flip through the real relay (demand → forbid → re-demand → accept) and fails on the pre-fix code.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant