Affected
inngest 4.6.0 through 4.15.0 (latest at time of filing), inngest/lambda serve handler. Any middleware passed via createFunction({ middleware: [...] }).
Summary
On AWS Lambda, middleware registered at the function level never has any of its hooks invoked — no error, no warning. The same middleware registered at the client level works fine. Functions execute normally either way, which makes the failure invisible until you depend on a middleware side effect. In our case it was @inngest/middleware-encryption (v2) attached per-function: events reached handlers as ciphertext, with no decryption error, because transformFunctionInput never ran.
Root cause
Two parts of the SDK disagree about where fnId lives.
- The Lambda adapter's
url() action rebuilds the request URL from the path only, discarding the query string (dist lambda.js):
url: () => {
const path = eventIsV2 ? event.requestContext.http.path : event.path;
const proto = getHeader("x-forwarded-proto") || "https";
return new URL(path, `${proto}://${getHeader("host") || ""}`);
}
InngestCommHandler attaches function-level middleware by matching fnId from that URL (dist components/InngestCommHandler.js, ~line 636):
const fnId = url.searchParams.get(queryKeys.FnId); // always null on Lambda
const matchedFn = fnId ? this.fns[fnId] : void 0;
const fnMw = matchedFn?.fn?.opts?.middleware ?? []; // silently []
Execution itself is unaffected because the run path resolves fnId via queryStringWithDefaults, which prefers the adapter's queryString action (event.queryStringParameters) over url.searchParams (~line 882). So the function runs — with client middleware only.
Repro
Serve any function via inngest/lambda (API Gateway HTTP API, payload 2.0) with a function-level middleware whose transformFunctionInput mutates the event (e.g. @inngest/middleware-encryption). The hook never fires on Lambda; the identical setup served via inngest/express (whose url() keeps the query string) works.
Suggested fix
Either include the query string when the Lambda adapter builds the URL (event.rawQueryString for v2 payloads, event.queryStringParameters for v1), or make the middleware-matching path resolve fnId via queryStringWithDefaults like the execution path does — the latter also fixes any other adapter with the same asymmetry.
Workaround
Wrap the serve handler and re-attach the query string to the event path before invoking it:
if (event.rawQueryString && !event.requestContext.http.path.includes("?")) {
event.requestContext.http.path += `?${event.rawQueryString}`;
}
Affected
inngest4.6.0 through 4.15.0 (latest at time of filing),inngest/lambdaserve handler. Any middleware passed viacreateFunction({ middleware: [...] }).Summary
On AWS Lambda, middleware registered at the function level never has any of its hooks invoked — no error, no warning. The same middleware registered at the client level works fine. Functions execute normally either way, which makes the failure invisible until you depend on a middleware side effect. In our case it was
@inngest/middleware-encryption(v2) attached per-function: events reached handlers as ciphertext, with no decryption error, becausetransformFunctionInputnever ran.Root cause
Two parts of the SDK disagree about where
fnIdlives.url()action rebuilds the request URL from the path only, discarding the query string (distlambda.js):InngestCommHandlerattaches function-level middleware by matchingfnIdfrom that URL (distcomponents/InngestCommHandler.js, ~line 636):Execution itself is unaffected because the run path resolves
fnIdviaqueryStringWithDefaults, which prefers the adapter'squeryStringaction (event.queryStringParameters) overurl.searchParams(~line 882). So the function runs — with client middleware only.Repro
Serve any function via
inngest/lambda(API Gateway HTTP API, payload 2.0) with a function-level middleware whosetransformFunctionInputmutates the event (e.g.@inngest/middleware-encryption). The hook never fires on Lambda; the identical setup served viainngest/express(whoseurl()keeps the query string) works.Suggested fix
Either include the query string when the Lambda adapter builds the URL (
event.rawQueryStringfor v2 payloads,event.queryStringParametersfor v1), or make the middleware-matching path resolvefnIdviaqueryStringWithDefaultslike the execution path does — the latter also fixes any other adapter with the same asymmetry.Workaround
Wrap the serve handler and re-attach the query string to the event path before invoking it: