Skip to content

Capacity and failure testing

Image generation is latency-bound, so capacity is primarily a concurrency model rather than a conventional high-RPS model.

sustainable operations/second ≈ provider concurrency / p95 provider seconds
finite provider admission envelope = max_concurrent + max_queued
finite global admission envelope = max_concurrent + max_queued

The default profile uses "unlimited", so the bridge does not create an admission envelope of its own. The upstream service and host resources remain real constraints. Operators who prefer overload shedding can set finite global and provider values from measured latency and account/provider limits, then keep client retries outside the bridge's bounded retry budget.

Offline harness

The Bun harness defaults to a deterministic local synthetic dependency. It does not read OAuth state or generate images. Every explicit --url, including loopback, is rejected unless --allow-live is supplied because it may trigger real paid/subscription-backed generations.

bun run tools/resilience-harness.ts --scenario load --json
bun run tools/resilience-harness.ts --scenario stress --json
bun run tools/resilience-harness.ts --scenario spike --json
bun run tools/resilience-harness.ts --scenario soak --soak-seconds 300 --json
bun run tools/resilience-harness.ts --scenario faults --json

The harness deliberately selects a finite synthetic envelope of 4 active plus 16 queued; it does not inherit the unlimited product default. Stress submits 60 concurrent operations—3x that envelope—and must degrade through fast 503s, remain live, drain every queue slot, and accept a recovery probe. Spike repeats the sudden burst. Soak runs at expected load. Faults inject 502, 429, disconnect analogue, and a hung call at the synthetic dependency boundary; it does not by itself claim to exercise bridge routing. Stress and spike pass only when both admitted successes and bounded overloads occur, all results stay inside the modeled envelope, and a recovery probe succeeds. The transport fault matrix is complemented by Rust integration tests that exercise bridge timeout, process exit, protocol failure, breaker, and fallback behavior through the real runtime.

A shortened pass is part of normal CI. The scheduled workflow runs the five-minute soak. Longer 1–24 hour runs should be started manually before choosing finite concurrency limits.

Example synthetic baseline

On 2026-07-19, seed 7 with a 15–25 ms synthetic dependency produced:

Scenario Result
Load, concurrency 4 40/40 success; p95 24 ms
Stress, concurrency 60 20 admitted, 100 bounded overloads; recovery passed
Spike, concurrency 80 20 admitted, 60 bounded overloads; recovery passed
Abbreviated soak 400/400 success; recovery passed
Fault matrix Four expected failures; recovery passed

This baseline proves the opt-in finite-capacity profile, not Codex throughput. Measure real provider p95 separately; do not load-test subscription-backed generation without explicit authorization.

Observed Codex latency

On the same host, three medium-quality live outputs observed before 0.2.0 took 94.8–119.3 seconds each, with 329.4 seconds reported by the provider across the batch and effectively zero bridge queue time. A final 0.2.0 low-quality single-output smoke took 42.27 seconds end to end: 42.26 seconds provider time, 0 milliseconds queue time, and about 10 milliseconds artifact/bridge work.

These samples are not an SLA. They were captured with the previous max_parallel_outputs = 2, where three outputs required two provider waves. The current "auto" default launches the three calls together. Browser ChatGPT may still use different orchestration, capacity, defaults, or presentation; matching its wall time is not a reliable bridge requirement.