Skip to content

feat: send Retry-After on 429 and expose it to the PHP client and the MCP server - #63

Merged
cristiangirlea merged 3 commits into
mainfrom
feat/retry-after
Oct 21, 2026
Merged

cristiangirlea merged 3 commits into
mainfrom
feat/retry-after

Conversation

@cristiangirlea

Copy link
Copy Markdown
Owner

Fixes #50.

A full store answered 429 capacity_exceeded with nothing a client could plan on; only the MCP server coped, guessing a doubling backoff. The store now knows when capacity returns: CapacityError carries retry_after in whole seconds (at least 1), computed as the earliest retention expiry among finished jobs, or 1 when only unfinished jobs fill the store, when shutting down, or when the job table cannot be written. Every 429 carries an integer Retry-After header.

BridgeException gains a nullable readonly retryAfterSeconds, its last constructor parameter with a default, filled by the client from the header on a 429 when it is one header of 1 to 6 digits; a repeated header yields null in both clients, since a conflicting pair is not a value to trust. Response headers are collected with a 16 KiB cap and refused as invalid_response beyond it, with the state reset at each status line so a 100 Continue block does not count. The client still never retries by itself. The MCP server's protocol client carries the parsed value and its capacity wait uses it when present, bounded by the remaining budget, keeping its own backoff otherwise. docs/protocol.md documents the header, docs/integration.md shows an application scheduling a resubmit from the property, and the changelog has an Unreleased line.

Files: worker/ai_bridge/jobs.py, worker/ai_bridge/server.py, src/BridgeException.php, src/Client.php, mcp_server/bridge_mcp/protocol.py, mcp_server/bridge_mcp/tools.py, tests/python/test_jobs.py, tests/python/test_server.py, tests/php/run.php, tests/php/integration.php (the fault worker filled to capacity and the header read through the real worker), tests/mcp/test_tools.py, tests/mcp/test_protocol.py, docs/protocol.md, docs/integration.md, docs/testing.md, CHANGELOG.md.

Checks on this head (merged with main after the SQLite store landed): python-tests 189 OK, php-tests 141, php -l, php-integration 178, FrankenPHP integration, mcp-tests 125 OK, composer-install, php-fetcher, php-analysis [OK] No errors. Reviews: independent review by Codex (gpt-6-luna), no defects, one untested edge (the repeated header) covered in this commit and confirmed mergeable on a second read.

# Conflicts:
#	CHANGELOG.md
#	docs/protocol.md
#	docs/testing.md
#	src/Client.php
#	tests/php/integration.php
#	tests/php/run.php
@cristiangirlea
cristiangirlea merged commit 1b3f9df into main Oct 21, 2026
7 checks passed
@cristiangirlea
cristiangirlea deleted the feat/retry-after branch October 21, 2026 06:42
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.

feat: send Retry-After on 429 and expose it to the PHP client and the MCP server

1 participant