Skip to content

Skip auto-inserting Rack::Lint on Rack 3+#183

Open
dduugg wants to merge 1 commit into
Shopify:masterfrom
dduugg:skip-auto-lint-on-rack-3
Open

Skip auto-inserting Rack::Lint on Rack 3+#183
dduugg wants to merge 1 commit into
Shopify:masterfrom
dduugg:skip-auto-lint-on-rack-3

Conversation

@dduugg
Copy link
Copy Markdown

@dduugg dduugg commented May 22, 2026

Problem

Pitchfork's default app builder injects Rack::Lint into the middleware stack whenever RACK_ENV == "development":

https://github.com/Shopify/pitchfork/blob/v0.18.2/lib/pitchfork.rb#L100

Combined with exe/pitchfork:7's ENV["RACK_ENV"] ||= "development", this means every Pitchfork process that didn't explicitly set RACK_ENV ends up with Rack::Lint in its stack.

On Rack 3, Rack::Lint::Wrapper::InputWrapper no longer exposes rewind (it was dropped from the strict spec). Rails middleware and most third-party Rack middleware still call request.body.rewind unconditionally, so any Rails dev process that switches from Puma to Pitchfork starts 500ing on every cookie- or body-bearing request:

undefined method 'rewind' for an instance of Rack::Lint::Wrapper::InputWrapper

This is #177 — the linked issue documents one observable failure (request.raw_post returns nil via Rack::MethodOverride exhausting the stream, then ActionDispatch::Request#reset_stream can't rewind). We hit it in a different shape: ActionDispatch::Cookies and third-party analytics middleware call rewind directly without respond_to?(:rewind) guards, so the failure happens before any controller runs, and the entire Rails GraphQL surface goes dark in dev.

Fix

Gate the auto-insert on Rack::RELEASE < "3". Rack 2 users keep the existing dev affordance. Rack 3 users stop tripping over the incompatibility.

The integration test uses the existing test/integration/fails-rack-lint.ru fixture (which returns a response header that Rack::Lint would 500 on). With lint: true (the spawn_server default — RACK_ENV stays at the CLI's "development" default), the test asserts the response comes through unchanged on Rack 3+, which is only true if the gate kept Rack::Lint out.

Alternatives considered

Issue #177 surfaced three other options, none of which I picked:

  • Drop ENV["RACK_ENV"] ||= "development" in exe/pitchfork. Cleanest from a Rails-convention standpoint (RAILS_ENV is the canonical source), but it's a behavior change for every Pitchfork user, not just Rack 3+ users, and it's load-bearing for the dev affordances on Rack 2.
  • Replace Rack::Lint with a custom validator that delegates rewind to the underlying input. More invasive, more code to maintain, and it leaks lint-spec violations of the rest of the spec we don't want to swallow.
  • Force users to set RACK_ENV=production (or equivalent) themselves. This is the workaround the issue lands on, but it's surprising — the trap fires the moment someone runs bin/pitchfork without reading the issue thread first.

The Rack-version gate is the minimum change that removes the trap for the affected users without changing anything for the unaffected ones. Happy to take it in a different direction if you'd prefer one of the alternatives.

Notes

  • Existing tests that explicitly opt out via lint: false (which sets RACK_ENV=production in the spawned process) are unaffected.
  • The single existing test that depends on Lint's strict behavior (test/integration/fails-rack-lint.ru is referenced only as a fixture; no test references it yet) is now covered by the new test — but only for the "Rack::Lint is not there" direction, which is what this PR introduces.

Pitchfork's default app builder injects Rack::Lint into the middleware
stack whenever RACK_ENV is "development" (which exe/pitchfork sets via
ENV["RACK_ENV"] ||= "development" if unset). On Rack 3, the strict
Rack::Lint::Wrapper::InputWrapper no longer exposes rewind, but Rails
middleware (cookies, body parsers) and most third-party Rack middleware
still call request.body.rewind unconditionally, so the moment a Rails
dev process switches from Puma to Pitchfork every cookie- or
body-bearing request 500s with

    undefined method 'rewind' for an instance of
    Rack::Lint::Wrapper::InputWrapper

The dev-affordance value of the auto-insert no longer outweighs the
trap on Rack 3+. Gate the insert on Rack::RELEASE < "3" so Rack 2 users
keep the existing behavior and Rack 3 users stop tripping over the
incompatibility. Adds an integration test using the existing
fails-rack-lint.ru fixture to lock the new behavior in.

Closes Shopify#177.
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.

1 participant