Skip to content

fix(auto-reply): auto-retry transient edge-block exhaustion + honest busy message#28

Merged
william-antoline-boon merged 3 commits into
boonfrom
fix/transient-edge-block-auto-retry
Jun 29, 2026
Merged

fix(auto-reply): auto-retry transient edge-block exhaustion + honest busy message#28
william-antoline-boon merged 3 commits into
boonfrom
fix/transient-edge-block-auto-retry

Conversation

@monac24

@monac24 monac24 commented Jun 26, 2026

Copy link
Copy Markdown

Summary

Fixes the user-facing symptom behind the arguijo incident (2026-06-26): the agent replied "⚠️ All models are temporarily rate-limited. Please try again in a few minutes." when the real cause was an upstream Cloudflare/Render edge 429 (HTML "Blocked" page) in front of boon-llm-gateway, not any LLM rate-limit.

Because arguijo routes every model through one gateway host, the edge 429 hit every fallback hop → chain_exhausted → the misleading terminal message. The models were never rate-limited, and a brief edge blip became a failed conversation.

What changed

  1. Auto-retry with exponential backoff. On an unexplained pure-transient chain exhaustion (every hop rate_limit/overloaded, no cooldown expiry, no usage-limit detail), retry the full primary→fallback chain up to with 2s/4s/8s backoff before surfacing anything. Extends the existing single transient-HTTP retry pattern in runAgentTurnWithFallback.
  2. Honest, non-revealing copy. Terminal message is now "⚠️ The AI service is briefly busy — retrying automatically… If it persists, please try again in a few minutes." — deliberately names no provider/edge/cause (no "rate-limit", "429", "Cloudflare", "Render", "gateway").
  3. Scoped gate. A known cooldown expiry or a Codex/usage-limit message is excluded from the retry path, so their actionable detail (countdown / reset time) still surfaces immediately instead of being silently retried away.

Classification is intentionally unchanged — an HTML-body 429 still classifies as rate_limit (per upstream openclaw#67517). This PR changes only retry behavior and user-facing copy, so the fork stays aligned with upstream classification.

Why not "fix the gateway / raise capacity"

Investigation ruled both out: gateway Go error paths are JSON-only (the HTML came from the edge in front of the process), Render app CPU/mem were flat ~0% during the window (not a capacity event), and arguijo sent only 67 requests all day from one fixed IP (can't have tripped a rate rule). The block originated at Cloudflare's view of Render's shared edge / AWS-EIP reputation — external to both the box and gateway compute. The durable in-our-control fix is this client-side resilience.

Tests

New tests in agent-runner-execution.test.ts:

  • message copy (no-expiry path → new wording; expiry path → countdown preserved)
  • resolveTransientRetryBackoffMs (2s/4s/8s) + MAX_TRANSIENT_BUSY_RETRIES === 3
  • retry-then-recover: two transient exhaustions then success → kind: success, 3 chain invocations, user sees no error
  • exhaust-then-friendly-message: all attempts fail → generic busy message, asserts no cause leakage (no rate-limit/429/cloudflare/render/gateway/"All models")
  • no-retry-when-cooldown-known: real cooldown surfaces countdown immediately, 1 invocation

Verification

  • agent-runner-execution.test.ts: 107/107 pass
  • oxfmt --check, oxlint (core): clean
  • tsgo:core + tsgo:test:src: clean (rc=0)

RCA: https://getboon.slite.com/app/docs/iki4aP3u37Xvot

🤖 Generated with Claude Code


Summary by cubic

Adds automatic retries for transient edge‑block (HTML 429) chain exhaustions and replaces the misleading rate‑limit copy with a truthful “briefly busy” notice to prevent failed turns during short gateway edge blocks.

  • Bug Fixes

    • Retries the full primary→fallback chain up to 5 times with equal‑jitter exponential backoff (base 5s, doubles, capped at 60s/step; ~67–135s total) when every hop is rate_limit/overloaded and no cooldown or usage‑limit detail exists.
    • Skips retries when a real cooldown expiry or Codex usage‑limit is present so countdown/details surface immediately.
    • Updates the terminal message to a neutral “briefly busy — retrying automatically…” and avoids provider/cause leakage; error classification remains unchanged.
  • Dependencies

    • Update baileys to 7.0.0-rc13 and remove the rc11 patch; teach scripts/postinstall-bundled-plugins.mjs to detect rc13’s native dispatcher guard so the hotfix no‑ops.
    • Security overrides to clear high/critical advisories: hono 4.12.27, form-data 2.5.6, protobufjs 7.6.4, plus @grpc/grpc-js 1.14.4, linkify-it 5.0.1, undici 7.28.0, vite 8.0.16, ws 8.21.0, @vitest/browser 4.1.9.

Written for commit 1e12efd. Summary will update on new commits.

Review in cubic

…busy message

When the model fallback chain exhausts on a pure-transient condition (every
hop rate_limit/overloaded) with no cooldown expiry or usage-limit detail, the
cause is almost always an upstream gateway/edge block — e.g. a Cloudflare/Render
HTML 429 that 429s every hop because they share one upstream host. Model-level
fallback can't help, and the old terminal message ("All models are temporarily
rate-limited") was both inaccurate (the models were never rate-limited) and
premature (a brief edge blip became a failed conversation).

- Retry the full primary→fallback chain up to 3x with exponential backoff
  (2s/4s/8s) on an *unexplained* pure-transient exhaustion before surfacing
  anything to the user. Extends the existing single transient-HTTP retry.
- Gate excludes a known cooldown expiry and Codex/usage-limit messages so their
  actionable detail still surfaces immediately rather than being retried away.
- Replace the terminal copy with honest, non-revealing wording that signals the
  automatic retry: "The AI service is briefly busy — retrying automatically…".
  Deliberately names no provider/edge/cause.

Classification is unchanged (HTML-429 still classifies as rate_limit, per
upstream openclaw#67517) — this only changes retry behavior and user-facing copy.

Tests: message copy (both paths), backoff helper, retry-then-recover,
exhaust-then-friendly-message (asserts no cause leakage), and no-retry-when-
cooldown-known. Full file 107/107; format/lint/tsgo core+test all green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@monac24

monac24 commented Jun 26, 2026

Copy link
Copy Markdown
Author

CI status

All checks this change can influence are green (67 passing): every checks-node-* test shard, check-prod-types, check-test-types, check-lint, and the architecture/boundary checks.

Two red checks are pre-existing/environmental, not caused by this 2-file diff:

  • security-fastpnpm audit --audit-level=high reports 17 high/critical advisories in production dependencies (vitest, undici, baileys, vite, ws, hono, protobufjs, …). None are touched by this PR; it fails identically on boon. Needs a dependency-bump PR, separate from this fix.
  • review — the Claude PR Review action errored with Could not fetch an OIDC token … id-token: write and an empty ANTHROPIC_API_KEY. Workflow-permissions/secrets misconfig on the fork, unrelated to the code.

Local verification on the diff: agent-runner-execution.test.ts 107/107, oxfmt --check clean, oxlint (core) clean, tsgo:core + tsgo:test:src rc=0.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

Re-trigger cubic

@gandalfboon gandalfboon Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — clean, well-scoped fix.

The core change is correct: isUnexplainedTransientEdgeBlock narrows precisely to pure-transient chain exhaustion with no cooldown expiry and no Codex/usage-limit detail — i.e. the upstream gateway/edge HTML-429 shape (every same-host hop 429s) where a blind chain retry can genuinely ride out the throttle window. Known cooldowns and usage-limits are deliberately excluded so their actionable countdown still surfaces immediately instead of being retried away. Placement at the tail of the error loop (last guard before terminal classification) with a bounded 3× 2s/4s/8s backoff via continue is right.

Honest copy fix is a real improvement — the old 'All models are temporarily rate-limited' was inaccurate (the models were never rate-limited) and the new wording leaks no provider/edge/cause.

Tests are thorough: retry-then-recover, exhaust-then-friendly with explicit no-leak assertions (rate-limit/429/cloudflare/render/gateway/waf/boon-llm-gateway all negated), no-retry-when-cooldown-known, and the backoff/cap math. No secrets, no migrations, no breaking callers.

Non-blocking note for follow-up: 2+4+8s = up to ~14s added latency before the user sees anything on a sustained outage — acceptable tradeoff that the PR description owns, and the auto-retry copy signals it. Ship it.

@monac24
monac24 requested review from augusteo and insomnius June 26, 2026 21:32
…ries

The initial 2s/4s/8s schedule was wrong for an edge rate-limiter / WAF
(Cloudflare in front of the gateway), the dominant trigger for pure-transient
chain exhaustion:
  - Retrying ~2s later almost always lands inside the same rate-limit window,
    and each retry re-feeds the very counter that is blocking us — fast retries
    can *prolong* the block rather than ride it out.
  - A fleet-wide fixed schedule synchronizes retries against one shared gateway
    host (thundering herd), which itself keeps the limiter tripped.

Redesign:
  - MAX_TRANSIENT_BUSY_RETRIES 3 → 5.
  - Base 5s, double per attempt, cap 60s/step.
  - Equal jitter (AWS "equal jitter": half fixed + half random). The fixed half
    guarantees a retry never fires too soon (no early re-trip); the random half
    de-synchronizes concurrent sessions sharing the edge.
  - rng is injectable for deterministic tests.
Recovery window grows from ~14s to ~67–135s — long enough to outlast a short
WAF/edge block instead of failing fast.

Tests: backoff bounds (un-jittered cap, min-jitter floor, [half,full] envelope),
retry count = 5. Full file 109/109; format/lint/tsgo core+test green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…st) (#29)

pnpm-audit-prod (CI "security-fast") flagged 17 high/critical advisories in
production deps. This clears 16, including the lone CRITICAL.

Transitive bumps via pnpm-workspace overrides (conservative, in-range, no major):
  @grpc/grpc-js 1.14.4 · linkify-it 5.0.1 · undici 7.28.0 · vite 8.0.16 ·
  ws 8.21.0 · @vitest/browser 4.1.9 · hono 4.12.18→4.12.27 ·
  form-data 2.5.4→2.5.6 · protobufjs 7.5.6→7.6.4

baileys 7.0.0-rc11 → rc13 (CRITICAL GHSA-qvv5-jq5g-4cgg):
  - bumped in extensions/whatsapp + release-age exclude
  - DROPPED patches/[email protected] — rc13 ships all three fixes
    natively (sequential-await encrypted-stream finish + dispatcher guard)
  - taught scripts/postinstall-bundled-plugins.mjs to recognize rc13's native
    dispatcher guard (hoisted `const dispatcher = … ? agent : undefined`) so
    the runtime hotfix no-ops instead of failing with "unexpected_content"

Deferred (1 remaining HIGH): @earendil-works/pi-coding-agent
(GHSA-jfgx-wxx8-mp94, LPE on shared Linux). Fix needs pi-* 0.75→0.78+, a
BREAKING pi-ai API change (streamSimple/completeSimple/getApiProvider/complete
removed) — a separate migration, not a security bump. Trial hosts are
single-tenant, so the shared-host LPE is low-risk in our deployment.

Verify: pnpm-audit-prod 17→1 · tsgo:core clean · postinstall hotfix no-ops ·
oxlint clean.

Co-authored-by: Claude Opus 4.8 <[email protected]>
@github-actions

Copy link
Copy Markdown

Dependency Changes Detected

This PR changes dependency-related files. Maintainers should confirm these changes are intentional.

Changed files:

  • extensions/whatsapp/package.json
  • patches/[email protected]
  • pnpm-lock.yaml
  • pnpm-workspace.yaml

Maintainer follow-up:

  • Review whether the dependency changes are intentional.
  • Inspect resolved package deltas when lockfile or workspace dependency policy changes are present.
  • Run pnpm deps:changes:report -- --base-ref origin/main --markdown /tmp/dependency-changes.md --json /tmp/dependency-changes.json locally for detailed release-style evidence.

@william-antoline-boon
william-antoline-boon merged commit 28596dd into boon Jun 29, 2026
93 of 94 checks passed
@william-antoline-boon
william-antoline-boon deleted the fix/transient-edge-block-auto-retry branch June 29, 2026 02:55
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.

2 participants