Found by the P1 anonymous walk while tightening the console-error assertion (#314, epic #307).
Symptom
Once /api/system/version exceeds its 10-req/min rate limit, the API rejects with HTTP 503, not 429:
$ for i in $(seq 1 12); do curl -s -o /dev/null -w "%{http_code} " https://api.spikersoft.com/api/system/version; done
200 200 200 200 200 200 200 200 200 200 503 503
The 503 is served by Kestrel itself (security headers + x-trace-id present), so it's the app's rate-limiting middleware — not Traefik.
Root cause
ASP.NET Core's built-in Microsoft.AspNetCore.RateLimiting middleware defaults RateLimiterOptions.RejectionStatusCode to 503 Service Unavailable. It was never set to 429.
Why it matters
The SPA's VersionService handles 429 gracefully (console.warn + "Unknown" fallback) but treats 503 as a generic failure and logs console.error — which now trips the E2E walk's hardened no-console-error assertion (each page load in the walk fetches the version, blowing the 10/min budget).
503 is semantically "service down" — it pollutes uptime monitoring and tells clients/proxies the wrong thing. Rate-limit rejections should be 429 (+ Retry-After).
Fix
Set options.RejectionStatusCode = StatusCodes.Status429TooManyRequests on the rate limiter registration (and ideally emit Retry-After). Watch middleware order re CORS headers on the rejection — same trap as #311.
E2E linkage
The anonymous walk allowlists this exact noise (e2e/support/fixtures.ts, EXPECTED_CONSOLE_NOISE) scoped to /api/system/version and referencing this issue — remove those entries once this ships.
Found by the P1 anonymous walk while tightening the console-error assertion (#314, epic #307).
## Symptom
Once `/api/system/version` exceeds its 10-req/min rate limit, the API rejects with **HTTP 503**, not 429:
```
$ for i in $(seq 1 12); do curl -s -o /dev/null -w "%{http_code} " https://api.spikersoft.com/api/system/version; done
200 200 200 200 200 200 200 200 200 200 503 503
```
The 503 is served by Kestrel itself (security headers + `x-trace-id` present), so it's the app's rate-limiting middleware — not Traefik.
## Root cause
ASP.NET Core's built-in `Microsoft.AspNetCore.RateLimiting` middleware defaults `RateLimiterOptions.RejectionStatusCode` to **503 Service Unavailable**. It was never set to 429.
## Why it matters
- The SPA's `VersionService` handles 429 gracefully (`console.warn` + "Unknown" fallback) but treats 503 as a generic failure and logs `console.error` — which now trips the E2E walk's hardened no-console-error assertion (each page load in the walk fetches the version, blowing the 10/min budget).
- 503 is semantically "service down" — it pollutes uptime monitoring and tells clients/proxies the wrong thing. Rate-limit rejections should be 429 (+ `Retry-After`).
## Fix
Set `options.RejectionStatusCode = StatusCodes.Status429TooManyRequests` on the rate limiter registration (and ideally emit `Retry-After`). Watch middleware order re CORS headers on the rejection — same trap as #311.
## E2E linkage
The anonymous walk allowlists this exact noise (`e2e/support/fixtures.ts`, `EXPECTED_CONSOLE_NOISE`) scoped to `/api/system/version` and referencing this issue — remove those entries once this ships.
spikerj
added the bug label 2026-07-03 18:03:28 +00:00
Fix open: spikersoft-backend PR #59 — RejectionStatusCode = 429 + Retry-After from lease metadata on the built-in limiter (the Redis limiter already returned 429). Will verify the live endpoint returns 429 after merge+deploy, then remove the two /api/system/version allowlist entries from EXPECTED_CONSOLE_NOISE in the E2E fixtures and close.
Fix open: spikersoft-backend PR #59 — `RejectionStatusCode = 429` + `Retry-After` from lease metadata on the built-in limiter (the Redis limiter already returned 429). Will verify the live endpoint returns 429 after merge+deploy, then remove the two `/api/system/version` allowlist entries from `EXPECTED_CONSOLE_NOISE` in the E2E fixtures and close.
Backend fix (spikersoft-backend PR #59) merged and verified live: /api/system/version now returns 9×200 then 429 with Retry-After: 60 (was 503).
Frontend follow-up open: spikersoft-angular PR #109 — VersionService no longer console.errors on the graceful 429/offline paths (the catch-all log fired before the warn branches), and the two 503-era allowlist entries are removed from EXPECTED_CONSOLE_NOISE (one narrowed entry remains for Chromium's browser-emitted 429 resource-load line, which app code can't suppress). Anonymous walk green against the live 429s (53/53 + 2 skips). Closing once #109 merges.
Backend fix (spikersoft-backend PR #59) merged and **verified live**: `/api/system/version` now returns 9×200 then `429` with `Retry-After: 60` (was 503).
Frontend follow-up open: spikersoft-angular PR #109 — `VersionService` no longer `console.error`s on the graceful 429/offline paths (the catch-all log fired before the warn branches), and the two 503-era allowlist entries are removed from `EXPECTED_CONSOLE_NOISE` (one narrowed entry remains for Chromium's browser-emitted 429 resource-load line, which app code can't suppress). Anonymous walk green against the live 429s (53/53 + 2 skips). Closing once #109 merges.
Both halves are now merged: spikersoft-backend PR #59 (429 + Retry-After — verified live) and spikersoft-angular PR #109 (VersionService warn-only on the 429/offline paths; 503-era allowlist entries removed, one narrowed browser-emitted-429 entry remains). Anonymous walk green against the live 429s. Closing.
Both halves are now merged: spikersoft-backend PR #59 (429 + `Retry-After` — verified live) and spikersoft-angular PR #109 (VersionService warn-only on the 429/offline paths; 503-era allowlist entries removed, one narrowed browser-emitted-429 entry remains). Anonymous walk green against the live 429s. Closing.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found by the P1 anonymous walk while tightening the console-error assertion (#314, epic #307).
Symptom
Once
/api/system/versionexceeds its 10-req/min rate limit, the API rejects with HTTP 503, not 429:The 503 is served by Kestrel itself (security headers +
x-trace-idpresent), so it's the app's rate-limiting middleware — not Traefik.Root cause
ASP.NET Core's built-in
Microsoft.AspNetCore.RateLimitingmiddleware defaultsRateLimiterOptions.RejectionStatusCodeto 503 Service Unavailable. It was never set to 429.Why it matters
VersionServicehandles 429 gracefully (console.warn+ "Unknown" fallback) but treats 503 as a generic failure and logsconsole.error— which now trips the E2E walk's hardened no-console-error assertion (each page load in the walk fetches the version, blowing the 10/min budget).Retry-After).Fix
Set
options.RejectionStatusCode = StatusCodes.Status429TooManyRequestson the rate limiter registration (and ideally emitRetry-After). Watch middleware order re CORS headers on the rejection — same trap as #311.E2E linkage
The anonymous walk allowlists this exact noise (
e2e/support/fixtures.ts,EXPECTED_CONSOLE_NOISE) scoped to/api/system/versionand referencing this issue — remove those entries once this ships.Fix open: spikersoft-backend PR #59 —
RejectionStatusCode = 429+Retry-Afterfrom lease metadata on the built-in limiter (the Redis limiter already returned 429). Will verify the live endpoint returns 429 after merge+deploy, then remove the two/api/system/versionallowlist entries fromEXPECTED_CONSOLE_NOISEin the E2E fixtures and close.Backend fix (spikersoft-backend PR #59) merged and verified live:
/api/system/versionnow returns 9×200 then429withRetry-After: 60(was 503).Frontend follow-up open: spikersoft-angular PR #109 —
VersionServiceno longerconsole.errors on the graceful 429/offline paths (the catch-all log fired before the warn branches), and the two 503-era allowlist entries are removed fromEXPECTED_CONSOLE_NOISE(one narrowed entry remains for Chromium's browser-emitted 429 resource-load line, which app code can't suppress). Anonymous walk green against the live 429s (53/53 + 2 skips). Closing once #109 merges.Both halves are now merged: spikersoft-backend PR #59 (429 +
Retry-After— verified live) and spikersoft-angular PR #109 (VersionService warn-only on the 429/offline paths; 503-era allowlist entries removed, one narrowed browser-emitted-429 entry remains). Anonymous walk green against the live 429s. Closing.