Skip to content

Bug: Warning callback-based function handlers caused by @tracer.captureLambdaHandler() #4306

@ncino-esselman

Description

@ncino-esselman

Expected Behavior

I would expect to be able to add @tracer.captureLambdaHandler() to my async lambda handler and not recieve the following warning

AWS Lambda plans to remove support for callback-based function handlers starting with Node.js 24. You will need to update this function to use an async handler to use Node.js 24 or later. For more information and to provide feedback on this change, see aws/aws-lambda-nodejs-runtime-interface-client#137. To disable this warning, set the AWS_LAMBDA_NODEJS_DISABLE_CALLBACK_WARNING environment variable.

Current Behavior

We recently started getting:

AWS Lambda plans to remove support for callback-based function handlers starting with Node.js 24. You will need to update this function to use an async handler to use Node.js 24 or later. For more information and to provide feedback on this change, see aws/aws-lambda-nodejs-runtime-interface-client#137. To disable this warning, set the AWS_LAMBDA_NODEJS_DISABLE_CALLBACK_WARNING environment variable.

We are not using callback based lambda handler definitions and after removing the @tracer.captureLambdaHandler() from our handler function the warning goes away.

Code snippet

  @tracer.captureLambdaHandler()
  @logger.injectLambdaContext()
  public async handler(event: any, context: Context): Promise<any> {
    await this.preRun(event, context);
    return await this.main(event, context);
  }



export const handler = new Handler();
export const main = handler.handler.bind(handler);

Steps to Reproduce

  1. Create a simple async handler
  2. Deploy and run and see no warning in logs
  3. Add @tracer.captureLambdaHandler() to handler() definition
  4. deploy and run and see the warning

Possible Solution

public captureLambdaHandler(
  options?: CaptureLambdaHandlerOptions
): HandlerMethodDecorator {
  return (_target, _propertyKey, descriptor) => {
    const originalMethod = descriptor.value!;
    const tracerRef = this;
    
    // Check if the original method expects a callback parameter
    const originalMethodLength = originalMethod.length;
    const expectsCallback = originalMethodLength >= 3;
    
    // Create the wrapper function with the appropriate signature
    if (expectsCallback) {
      // Original method expects callback - create wrapper with callback parameter
      descriptor.value = function (this: Handler, event, context, callback) {
        // ... existing logic with callback parameter
      } as SyncHandler<Handler> | AsyncHandler<Handler>;
    } else {
      // Original method doesn't expect callback - create wrapper WITHOUT callback parameter
      descriptor.value = function (this: Handler, event, context) {
        // ... existing logic without callback parameter
      } as SyncHandler<Handler> | AsyncHandler<Handler>;
    }

    return descriptor;
  };
}

ref

public captureLambdaHandler(
options?: CaptureLambdaHandlerOptions
): HandlerMethodDecorator {
return (_target, _propertyKey, descriptor) => {
// biome-ignore lint/style/noNonNullAssertion: The descriptor.value is the method this decorator decorates, it cannot be undefined.
const originalMethod = descriptor.value!;
const tracerRef = this;
// Use a function() {} instead of an () => {} arrow function so that we can
// access `myClass` as `this` in a decorated `myClass.myMethod()`.
descriptor.value = function (this: Handler, event, context, callback) {
if (!tracerRef.isTracingEnabled()) {
return originalMethod.apply(this, [event, context, callback]);
}
return tracerRef.provider.captureAsyncFunc(
`## ${process.env._HANDLER}`,
async (subsegment) => {
tracerRef.annotateColdStart();
tracerRef.addServiceNameAnnotation();
let result: unknown;
try {
result = await originalMethod.apply(this, [
event,
context,
callback,
]);
if (options?.captureResponse ?? true) {
tracerRef.addResponseAsMetadata(result, process.env._HANDLER);
}
} catch (error) {
tracerRef.addErrorAsMetadata(error as Error);
throw error;
} finally {
try {
subsegment?.close();
} catch (error) {
console.warn(
'Failed to close or serialize segment %s. We are catching the error but data might be lost.',
subsegment?.name,
error
);
}
}
return result;
}
);
} as SyncHandler<Handler> | AsyncHandler<Handler>;
return descriptor;
};
}

Powertools for AWS Lambda (TypeScript) version

latest

AWS Lambda function runtime

22.x

Packaging format used

npm

Execution logs

INIT_START Runtime Version: nodejs:20.v70	Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:c11827d1307a63ca6bbb996dbffba66628e1328ed4714070f4b891884a323e0b
2025-08-11T15:27:00.879Z	undefined	WARN	AWS Lambda plans to remove support for callback-based function handlers starting with Node.js 24. You will need to update this function to use an async handler to use Node.js 24 or later. For more information and to provide feedback on this change, see https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/issues/137. To disable this warning, set the AWS_LAMBDA_NODEJS_DISABLE_CALLBACK_WARNING environment variable.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingcompletedThis item is complete and has been merged/shippedtracerThis item relates to the Tracer Utility

Type

No type

Projects

Status

Shipped

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions