fix(ferndesk): bound + honour Retry-After on article write 429s (COR-444) - #15
Merged
Merged
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
…444)
The write path (POST /articles, PATCH /articles/{id}, publish, collection
create) retried 429s with a fixed ladder and ignored Retry-After, so a
rate-limited run could stall or hang without reporting.
- Retry-After (delta-seconds or HTTP-date) now drives the wait when the
server sends it and it exceeds the exponential backoff; values over 300s
are treated as unusable and fall back to backoff.
- Bounded three ways: FERNDESK_WRITE_RETRIES (12/write),
FERNDESK_WRITE_DEADLINE (1800s/write), FERNDESK_WRITE_BUDGET (5400s/run).
Hard 4xx still fails fast without retrying.
- Retry logs name the article slug, status, and why it waited.
- A page that exhausts its retries no longer aborts the run: the sync
continues, logs FAILURES, records failed/failed_slugs in SUMMARY, exits 1,
and caches nothing for that slug so the next run retries cleanly.
Builds on the parallel COR-444 fix already on main (#14), which raised the
429 retry count and added a slower 429 cool-down. This keeps that 5s-to-180s
429 ladder and the create-conflict slug lookup + PATCH recovery, and layers
Retry-After handling, the per-write deadline, the run-wide budget, and honest
failure reporting on top. Pacing (1.2s/1.5s) and limit=100 pagination from
#14 are unchanged.
scripts/tests/ferndesk-sync-retry.test.py covers the policy offline (faked
transport, virtual clock) — 42 checks, including #14's conflict recovery —
and runs in the Docs site CI job.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
echobt
force-pushed
the
droid/665cde33-cor-444-ferndesk-post-articl
branch
from
September 14, 2026 22:00
8797f54 to
fbace52
Compare
Contributor
Author
|
@greptileai review — rebased onto main (f2e9f08) and reconciled with #14: Retry-After handling, per-write deadline, run-wide write budget, and honest FAILURES reporting layered on top of #14's 5s→180s 429 ladder and create-conflict slug lookup + PATCH recovery. |
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
COR-444 residual, write side.
#13(b589269) fixed the infinite/collectionspagination; this PR fixes the other half of the same ticket: the article write path.POST /articlesreturns429/{"code":"rate_limited"}under load. The retry loop existed but:Retry-Afterentirely,rate/limit 429with no article identity,Changes
scripts/ferndesk_sync.pyRetry-Afteris honoured when the server sends it and it exceeds the exponential backoff. Both formats parse: delta-seconds and HTTP-date. Values over 300s are treated as unusable and fall back to the backoff ladder, so a hostile header cannot park CI.FERNDESK_WRITE_RETRIES8FERNDESK_WRITE_DEADLINE900FERNDESK_WRITE_BUDGET36000disables)HTTP 429/cf1010/403/ transport), and why it waited (Retry-Aftervsbackoff).FAILURESline, recordsfailed+failed_slugsin the SUMMARY, and exits1. Nothing is cached for a failed write, so the next run retries that slug cleanly.scripts/tests/ferndesk-sync-retry.test.py(new)42 offline checks: faked transport, virtual clock, no network and no real waits. Covers
Retry-Afterparsing (absent/blank/garbage/seconds/negative/HTTP-date), the backoff ladder and cap, the over-cap fallback, 429-then-success, exhausted retries, the per-write deadline, the run-wide budget, fail-fast on 400, the read budget, and an end-to-end run where a page stays rate-limited..github/workflows/ci.ymlruns that test in the Docs site job.scripts/FERNDESK.mddocuments the knobs.Test plan
No live API write was attempted from this session: the fix is verified against a faked transport, and the real
SUMMARY-green run needs theFERNDESK_API_KEYsecret in Actions. This PR does not claim Comp Done.Separate Comp / ops items (not blocking this PR)
docs.cortex.foundationin the FernDesk UI (HTTPS). Unrelated to the CF 1010 API path.FERNDESK_DOCS_DISPATCH_TOKEN— the backend production-deploy hook that firesrepository_dispatch: ferndesk-sync.Both remain open Comp/ops work; the sync cannot report SUMMARY-green end to end until the secret is present and the rate limit is not saturating.
@greptileai