Skip to content

fix(nginx): pass app 429 responses through - #645

Open
balamurali27 wants to merge 1 commit into
developfrom
fix/pass-through-app-429
Open

balamurali27 wants to merge 1 commit into
developfrom
fix/pass-through-app-429

Conversation

@balamurali27

Copy link
Copy Markdown
Contributor

Summary

error_page 429 replaced every 429 with the "Daily Usage Limit Reached" page. nginx then dropped the app's body and headers (Retry-After, X-RateLimit-*), so API clients of custom rate limiters cannot back off.

Now only frappe's own plan rate limit response (rate_limiter.respond(): 429, bare text/plain "Too Many Requests") gets our page. The swap uses sub_filter, the same method as the 500 page.

 bench @webserver (both server blocks)
+  sub_filter 'Too Many Requests' $exceeded_page   # only when 429 + text/plain
+  sub_filter_types text/plain
+  more_set_headers -s 429 -t text/plain "Content-Type: text/html"
-proxy_intercept_errors on; error_page 429 /exceeded.html   (bench, standalone)
+location = /exceeded.html   # public now, the iframe loads it
 proxy
-error_page 429 /exceeded.html   # 502/504 interception stays

Evidence

Rendered bench template on openresty 1.27.1.2, with a fake upstream:

Upstream Before After
429 text/plain "Too Many Requests" (frappe limiter) exceeded page exceeded page (iframe, text/html)
429 application/json + Retry-After, X-RateLimit-* exceeded page, app headers dropped body and headers unchanged
200 text/plain "Too Many Requests" unchanged unchanged
500 werkzeug page 500 page 500 page
GET /exceeded.html 404 (internal) 200

Merge Danger

Door: two-way

Revert and regenerate the nginx configs.

Blast Radius: wide

  • Every bench and proxy nginx config.
  • Until a bench config is generated again, sites behind an updated proxy show frappe's plain "Too Many Requests" in place of our page.
  • frappe translates "Too Many Requests", so sites in other languages get frappe's text and not our page.
  • Every text/plain 429 goes out as text/html. A plain-text app 429 keeps its body.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LTWXzC9v7otLpaxALSTvEN

error_page 429 replaced every 429 with the "Daily Usage Limit Reached"
page and dropped the app's body and headers (Retry-After, X-RateLimit-*),
so api clients of custom rate limiters could not back off. sub_filter now
swaps only frappe's own plan rate limit response (bare text/plain "Too
Many Requests"), like the 500 page. the proxy stops intercepting 429.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LTWXzC9v7otLpaxALSTvEN
@mergify

mergify Bot commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@greptile-apps

greptile-apps Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change affects 429 responses across sites served by these nginx configs.

Fix custom plain-text 429 body replacement before merging.

Reviews (1) · Last reviewed commit: "fix(nginx): pass app 429 responses throu..." · Reviewed by Greptile


sub_filter '<h1>Internal Server Error</h1>' $internal_server_error_page_{{ bench_name_slug }};
sub_filter '<h1><p>Internal Server Error</p></h1>' $gunicorn_internal_server_error_page_{{ bench_name_slug }};
sub_filter 'Too Many Requests' $exceeded_page_{{ bench_name_slug }};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Custom limiter messages get replaced

sub_filter matches a phrase, not the bare Frappe response. A custom text/plain 429 such as Too Many Requests. Retry in 30 seconds. gets part of its message replaced with an iframe in both server blocks. Restrict the replacement to the known Frappe response so custom limiters keep their full body.

Prompt To Fix With AI
This is a comment left during a code review.
Path: agent/templates/bench/nginx.conf.jinja2
Line: 185

Comment:
**Custom limiter messages get replaced**

`sub_filter` matches a phrase, not the bare Frappe response. A custom `text/plain` 429 such as `Too Many Requests. Retry in 30 seconds.` gets part of its message replaced with an iframe in both server blocks. Restrict the replacement to the known Frappe response so custom limiters keep their full body.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant