Skip to content

[BUG] Function-level middleware silently never runs on AWS Lambda — lambda adapter's url() drops the query string #1673

Description

@ashishtilara

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.

  1. 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") || ""}`);
}
  1. 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}`;
}

Metadata

Metadata

Assignees

Labels

BugSomething isn't workingSDKTS SDK📦 inngestAffects the `inngest` package

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions