Skip to content

fix(combo): per-target skip diagnostics for ALL_TARGETS_SKIPPED + tiny-budget reasoning probe returns truncated 200 - #12661

Closed
alvinveroy wants to merge 10 commits into
cheaper-inference:release/v3.8.51from
alvinveroy:combo-skip-diagnostics
Closed

alvinveroy wants to merge 10 commits into
cheaper-inference:release/v3.8.51from
alvinveroy:combo-skip-diagnostics

Conversation

@alvinveroy

Copy link
Copy Markdown
Contributor

Closes #12659. Implements the diagnostics ask in #12294; #10281 combo-path parity.

Production trigger

2026-09-03: 1419× 503 ALL_TARGETS_SKIPPED in one hour (diagnostics.attempted=0, excluded=[]) — after the storm was over, the only actionable causes (an API-key allowlist mismatch silently skipping the one healthy target; multi-day persisted cooldowns) were invisible at the API surface and lived in debug-only logs.

Change

  • decisionTrace.ts: persisted_cooldown skip reason + optional detail + pure summarizeSkippedTargets().
  • Both combo dispatchers: persisted-cooldown skips record a trace decision; buildComboDiag() emits skippedTargets[] (target, reason, detail) + nextRetryAt (earliest future cooldown reset parsed from the detail) so clients can back off instead of hammering.
  • chat.ts: allowlist rejections during target screening log at info with the key name (the mismatch class behind this incident: alias wildcard bailian/* vs dynamic openai-compatible-chat-<uuid> provider id).
  • Tiny-budget reasoning probes (max_tokens < 256 targeting a thinking model) whose 200 burned the whole budget on thinking return a truncated 200 (finish_reason: "length") via buildReasoningProbeTruncatedResponse — fix(api): answer tiny-budget reasoning probes (max_tokens:1) with truncated 200 — no 500 empty response, no cooldown poisoning #10281 direct-path parity; stops fallback burn + model-lockout poisoning (68× 502 in one day).

Testing

tests/unit/12294-skipped-target-diagnostics.test.ts (5) + tests/unit/10281-combo-reasoning-probe.test.ts (3) + full tests/unit/combo/* and reasoning suites green.

…ode background identity headers + staged resilience work

# Conflicts:
#	open-sse/executors/base.ts
#	open-sse/handlers/embeddings.ts
#	open-sse/services/combo.ts
#	open-sse/services/opencodeQuotaFetcher.ts
#	src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx
#	src/i18n/messages/in.json
#	src/lib/usage/callLogArtifacts.ts
#	src/shared/constants/visionModels.ts
#	src/shared/utils/httpClientAbortGuard.mjs
#	tests/unit/call-log-cap.test.ts
#	tests/unit/httpClientAbortGuard.test.mjs
…10, with a first-byte watchdog

Root cause of the 2026-09-03/04 outage: with ENABLE_TLS_FINGERPRINT=true every
direct inference dispatch rode the wreq-js (browser-JA3) transport, whose
responses resolved but streamed ZERO bytes for 90-600s on opencode-go /
command-code / bailian (agents aborted -> 499 storms; the then-live 110s
headers-wait default turned the hangs into 504s). Plain node fetch streamed the
same requests in seconds and control-plane calls were unaffected — the stall is
transport+streaming-specific, not DNS/CF slow-walking.

- wreq is no longer eager by default: plain direct fetch is the fast path; a
  Cloudflare fingerprint rejection (403 + 1010 / browser_signature_banned)
  arms a 1h per-provider cache and retries once via wreq (replay-safe requests
  only). TLS_FINGERPRINT_PROVIDERS keeps its legacy eager semantics, now
  guarded.
- Every wreq response (direct, ladder retry, proxied overlay) carries a
  first-byte watchdog (TLS_FINGERPRINT_FIRST_BYTE_WATCHDOG_MS, default 10s):
  no first byte -> cancel and fall back to the direct dispatcher instead of
  hanging the caller.

Verified live: fingerprint off + ladder armed-on-demand restores TTFB 3-6s on
opencode-go (was 90-600s of zero bytes), zero 1010 recurrence in the window.
…y-budget reasoning probe (cheaper-inference#12294, cheaper-inference#10281)

Production evidence (2026-09-03): smart/yano-free/yano-openweights 503'd 1419x
in one hour with diagnostics.attempted=0, excluded=[] — operators could not
see WHY every target was pre-dispatch filtered (cheaper-inference#12294).

- decisionTrace: persisted_cooldown skip reason + entry detail + pure
  summarizeSkippedTargets() aggregator.
- combo.ts (both dispatchers): persisted-cooldown skips record a trace
  decision; buildComboDiag() emits skippedTargets[] + nextRetryAt (earliest
  cooldown reset) in the 503 body.
- chat.ts: API-key allowlist rejections during combo target screening log at
  info with the key name (was debug-only; an allowlist mismatch silently
  emptied the pool).
- validateQuality + combo dispatchers: a tiny-budget reasoning probe
  (max_tokens < 256) whose upstream 200 burned the whole budget on thinking
  returns a truncated 200 (finish_reason length) instead of 502 + model
  lockout (cheaper-inference#10281 combo-path parity).
- glm [1214] / reasoning-presence fixes live in the sibling
  opencode-identity-headers branch.
… watchdog

2026-09-05 wedge incident: opencode h2 stream resets (ERR_HTTP2_STREAM_ERROR,
UND_ERR_SOCKET) fanned out through every combo target (8 fallbacks / 18
decisions per request), then a client abort during combo cleanup left the
event loop wedged — listener accepting, nothing served, nothing logged, for
hours. The existing supervisor only restarts on process exit.

- Bounded transport abort (both dispatchers): consecutive transport-class
  network failures share the same egress — after
  DEFAULT_TRANSPORT_ABORT_BOUND (3), abort the combo with an explicit 502
  instead of burning the remaining fan-out. Non-transport failures reset the
  counter. New predicates isTransportClassNetworkError /
  shouldAbortComboForTransportFailures in comboPredicates.ts.
- Self-probe wedge watchdog (scripts/dev/self-probe-watchdog.mjs, wired in
  standalone-server-ws.mjs after the listener accepts): loopback probe of
  /api/status on the same event loop — a wedged loop cannot answer it.
  N consecutive connect/timeout failures (any HTTP response counts as alive,
  incl. 401) exit(1) so launchd KeepAlive / systemd / Docker relaunch.
  Config: OMNIROUTE_SELF_PROBE(_INTERVAL_MS|_TIMEOUT_MS|_THRESHOLD), default
  60s/15s/3; OMNIROUTE_SELF_PROBE=off disables.
- file-size rebaseline for the touched frozen files with justification.
…in TLS watchdog; stryker tap registration

- dashboard/combos/page.tsx: defer the post-hydration localStorage correction
  one microtask so setShowUsageGuide is not a synchronous setState inside the
  effect body (react-hooks/set-state-in-effect base red on release/v3.8.51
  from cheaper-inference#12448); eslint:json gate green.
- proxyFetch.ts withTlsFirstByteWatchdog: the watchdog race consumed the first
  body chunk from the reader but never re-emitted it — the first SSE frame
  was silently dropped. The rest stream now re-emits the captured chunk
  before continuing the original stream.
- stryker.conf.json: register 10281-combo-reasoning-probe + transport-bounded-abort
  in tap.testFiles (they cover mutated modules; --strict gate).
- callLogArtifacts.ts / earlier dashboard stray: restore pristine remote state
  for files untouched by this branch (resolves unused-var lint).
…-diagnostics

# Conflicts:
#	config/quality/file-size-baseline.json
@alvinveroy

Copy link
Copy Markdown
Contributor Author

Superseded by #12531 — consolidating the 3.8.51 fork batch. This head branch carried the same cumulative content (this PR's topic + the TLS-fingerprint, opencode-identity, and ops work) on an older base and is CONFLICTING with current release/v3.8.51. #12531 now holds the full updated batch merged onto the latest release/v3.8.51 (17 upstream fixes), all gates green locally (changelog integrity, docs-all, agent-skills sync, file-size), with per-area change notes and tests. Closing as duplicate; reopen only if you want this topic split out separately.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(combo): ALL_TARGETS_SKIPPED must carry per-target skip reasons; tiny-budget reasoning probes should return truncated 200s

1 participant