pulumi/pulumi · warning · Error

${leakMessage}

Error message

${leakMessage}

What it means

After the Pulumi CLI process exits, the language server checks debuggable.leakedPromises() for promises that were never awaited. If the program exited cleanly but promises leaked (indicating work that silently never completed), the server throws with a diagnostic message describing the leaks. This is suppressed when the program already errored to keep output clean.

Source

Thrown at sdk/nodejs/automation/server.ts:46

 * @internal
 */
export class LanguageServer<T> implements grpc.UntypedServiceImplementation {
    readonly program: () => Promise<T>;

    // Satisfy the grpc.UntypedServiceImplementation interface.
    [name: string]: any;

    constructor(program: () => Promise<T>) {
        this.program = program;
    }

    onPulumiExit(hasError: boolean) {
        // Check for leaks once the CLI exits but skip if the program otherwise
        // errored to keep error output clean
        if (!hasError) {
            const [leaks, leakMessage] = debuggable.leakedPromises();
            if (leaks.size !== 0) {
                throw new Error(leakMessage);
            }
        }
    }

    getRequiredPlugins(call: any, callback: any): void {
        const resp: any = new langproto.GetRequiredPluginsResponse();
        resp.setPluginsList([]);
        callback(undefined, resp);
    }

    run(call: any, callback: any): Promise<void> {
        const req: any = call.request;
        const resp: any = new langproto.RunResponse();

        // Setup a new async state store for this run
        return localState.withLocalStorage(async () => {
            const errorSet = new Set<Error>();
            const uncaughtHandler = newUncaughtHandler(errorSet);

View on GitHub (pinned to 793f7b2e16)

Solutions

  1. Await every promise created in the program, especially inside apply callbacks.
  2. Check the leakMessage for the stack trace identifying the un-awaited call site.
  3. Enable promise-leak debugging by following the message's pointer to where the promise was created.

Example fix

// before
bucket.onObjectCreated('handler', handler); // returns promise, not awaited
// after
const done = bucket.onObjectCreated('handler', handler);
await done; // or ensure it resolves before program end
Defensive patterns

Strategy: try-catch

Validate before calling

process.on('unhandledRejection', (r) => console.error('leaked promise:', r));

Try / catch

try { await program(); } catch (e) { if (String(e.message).includes('leaked')) { console.error(e.message); process.exitCode = 1; } else throw e; }

Prevention

When it happens

Trigger: A Node.js Pulumi program registers a promise (e.g. an un-awaited async call or .then chain) that is still pending/unhandled when the program finishes; onPulumiExit then throws with the leak report.

Common situations: Forgetting to await an output.apply side effect or an async function call in the program; fire-and-forget operations like exports of unresolved values.

Related errors


AI-assisted analysis of pulumi/pulumi@793f7b2e16 (2026-08-31). Data as JSON: /api/errors/077220c6882dff7f. Report an issue: GitHub.