Skip to content

[finding] the HTTP header blocks in error-handling.mdx teach X-RateLimit-* headers no producer sets, and a Retry-After on 503 that no 503 producer sets #17188

Description

@baozhoutao

Found while fixing #16937, which corrects the details bags in the same fences. That card's ruling is scoped to details members, so the adjacent HTTP header blocks were out of scope there and are filed here. Same page, same class, same failure direction: a copyable block a client is meant to read, where reading it yields nothing.

Measured on origin/main at fd5cff209f.

The claims

content/docs/protocol/kernel/error-handling.mdx carries three header blocks:

  • under #### RATE_LIMIT_EXCEEDED and again under ### Rate Limit Exceeded
    X-RateLimit-Limit: 1000, X-RateLimit-Remaining: 0, X-RateLimit-Reset: 1705412460
  • under #### SERVICE_UNAVAILABLERetry-After: 300

Readings, each with a positive control on the same corpus

reading result positive control on the same corpus
X-RateLimit anywhere in packages/** 1 hit, not a producer Retry-After in packages/**: 142
any header-set of a ratelimit-named header (res.header / setHeader / headers[...]) tree-wide 0 same forms for Retry-After: 5
Retry-After within 10 lines of any 503 site (non-test, non-CHANGELOG) 0 identical technique on 429 sites: 2

Widening X-RateLimit from packages/** to the whole tree gives 31 hits, and every one is about GitHub's own API quota headers, not an ObjectStack response — they sit in scripts/pm/check-clause2-carriers.mjs and .github/workflows/docs-drift-check.yml. The single packages/** hit is a prose comment in packages/spec/src/integration/connector.test.ts:656 about upstream connector header names. No ObjectStack producer sets an X-RateLimit-* header on any response.

The Retry-After row is the more interesting one, because the header itself is real: both 429 producers set it —

packages/runtime/src/endpoint-policy.ts        headers: { 'Retry-After': String(retryAfterSec) }
packages/runtime/src/security/inbound-rate-limit.ts   res.header('Retry-After', String(retryAfterSec))

— so the control fires and the zero on the 503 side is a reading, not a dead grep. The five real 503 producers (packages/objectql/src/action-activation.ts:269, packages/triggers/trigger-api/src/api-trigger.ts:204, packages/plugins/plugin-auth/src/auth-plugin.ts:2260 and :2883) set no Retry-After and attach no details bag.

Consumer harm

A client written from the 429 header block paces itself on X-RateLimit-Remaining and reads null on every real 429 — so it does not pace at all, which is the opposite of what the block is teaching. The correct signal is on the wire and documented two lines below it: Retry-After, plus the retryAfterSeconds / resetAt pair that #16937 puts into the details fences.

A client written from the 503 block waits on a Retry-After that never arrives.

Why nothing catches it

Same gap #16937 and #17187 record on this page: these are prose fences, so neither the type-check nor any schema reaches them, and no gate compares a documented response header against the headers any producer sets.

Suggested fix

Drop the X-RateLimit-* lines from both 429 header blocks, keeping Retry-After, and drop the 503 header block. Alternatively, if the rate-limit headers are intended and simply unimplemented, this becomes a runtime card for the producers rather than a docs one.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions