Problem
The ValidatesInboundRequest docblock guarantees: "A validation failure returns a 422 response automatically and the request is never forwarded upstream." PassageController::handle() implements this with plain $request->validate($handlerInstance->rules()):
if ($handlerInstance instanceof ValidatesInboundRequest) {
$request->validate($handlerInstance->rules());
}
Request::validate() throws Illuminate\Validation\ValidationException on failure. Laravel's default exception handler only converts this to a JSON 422 when $request->expectsJson() is true (i.e. an Accept: application/json header, or an AJAX/PJAX request). Otherwise it calls $this->invalid($request, $e), which does redirect(url()->previous())->withInput(...). url()->previous() and withInput() both call $request->session(), which throws RuntimeException('Session store not set on request.') on any request that hasn't gone through the StartSession middleware — the normal case for an API-gateway-style route registered without the web middleware group.
So for any client that doesn't send Accept: application/json (a plain curl -X POST, many webhook senders, non-fetch/XHR form posts), a validation failure on a Passage route without session middleware crashes with an uncaught RuntimeException instead of the documented 422.
The only existing test (tests/Feature/PassageIntegrationTest.php, ValidatesInboundRequest describe block) uses postJson(), which always sends Accept: application/json and therefore never exercises this path.
Location
src/Http/Controllers/PassageController.php:96-98
src/Contracts/ValidatesInboundRequest.php (the docblock making the unconditional 422 promise)
tests/Feature/PassageIntegrationTest.php:166-186 (only covers the JSON-expecting case)
Why it matters
Passage is an API gateway; its routes are typically registered outside the web middleware group and callers won't reliably send Accept: application/json. The gap turns an expected validation error into an unhandled crash, which is a worse failure mode than skipping validation entirely.
Suggested fix
Wrap the $request->validate() call and catch ValidationException explicitly, returning response()->json(['error' => ..., 'errors' => $e->errors()], 422) unconditionally rather than relying on Laravel's default (session-dependent) redirect behavior. Add a test that submits invalid input without an Accept: application/json header (e.g. plain post()) to a ValidatesInboundRequest handler and asserts a 422 JSON response rather than a crash.
Problem
The
ValidatesInboundRequestdocblock guarantees: "A validation failure returns a 422 response automatically and the request is never forwarded upstream."PassageController::handle()implements this with plain$request->validate($handlerInstance->rules()):Request::validate()throwsIlluminate\Validation\ValidationExceptionon failure. Laravel's default exception handler only converts this to a JSON 422 when$request->expectsJson()is true (i.e. anAccept: application/jsonheader, or an AJAX/PJAX request). Otherwise it calls$this->invalid($request, $e), which doesredirect(url()->previous())->withInput(...).url()->previous()andwithInput()both call$request->session(), which throwsRuntimeException('Session store not set on request.')on any request that hasn't gone through theStartSessionmiddleware — the normal case for an API-gateway-style route registered without thewebmiddleware group.So for any client that doesn't send
Accept: application/json(a plaincurl -X POST, many webhook senders, non-fetch/XHR form posts), a validation failure on a Passage route without session middleware crashes with an uncaughtRuntimeExceptioninstead of the documented 422.The only existing test (
tests/Feature/PassageIntegrationTest.php,ValidatesInboundRequestdescribe block) usespostJson(), which always sendsAccept: application/jsonand therefore never exercises this path.Location
src/Http/Controllers/PassageController.php:96-98src/Contracts/ValidatesInboundRequest.php(the docblock making the unconditional 422 promise)tests/Feature/PassageIntegrationTest.php:166-186(only covers the JSON-expecting case)Why it matters
Passage is an API gateway; its routes are typically registered outside the
webmiddleware group and callers won't reliably sendAccept: application/json. The gap turns an expected validation error into an unhandled crash, which is a worse failure mode than skipping validation entirely.Suggested fix
Wrap the
$request->validate()call and catchValidationExceptionexplicitly, returningresponse()->json(['error' => ..., 'errors' => $e->errors()], 422)unconditionally rather than relying on Laravel's default (session-dependent) redirect behavior. Add a test that submits invalid input without anAccept: application/jsonheader (e.g. plainpost()) to aValidatesInboundRequesthandler and asserts a 422 JSON response rather than a crash.