pulumi/pulumi · error · Error

function is not a bound function

Error message

function is not a bound function

What it means

During closure serialization the SDK inspects a captured function to determine its runtime/remote execution metadata. When the V8 inspector reports the function has no [[TargetFunction]] internal property, it is not a bound function (created via Function.prototype.bind), so the SDK cannot unwrap the original target and throws. This code path expects `func` to be a bound function whose target can be resolved.

Source

Thrown at sdk/nodejs/runtime/closure/v8.ts:336

    }

    // Extract value and clear our table entry.
    const val = context.calls[tableId];
    delete context.calls[tableId];

    return val;
}

export async function getBoundFunction(
    func: Function,
): Promise<{ targetFunctionText: string; boundThisValue: any; boundArgsValues: any[] }> {
    const functionId = await getRuntimeIdForFunctionAsync(func);
    const { internalProperties } = await runtimeGetPropertiesAsync(functionId, /*ownProperties:*/ false);

    const desc = internalProperties.find((p) => p.name === "[[TargetFunction]]");
    const targetFunctionText = desc?.value?.description;
    if (!targetFunctionText) {
        throw new Error("function is not a bound function");
    }

    const boundThisValue = internalProperties.find((p) => p.name === "[[BoundThis]]")?.value?.value;

    const boundArgsObjectId = internalProperties.find((p) => p.name === "[[BoundArgs]]")?.value?.objectId;
    let boundArgsValues: any[] = [];
    if (boundArgsObjectId) {
        const { properties } = await runtimeGetPropertiesAsync(boundArgsObjectId, /*ownProperties:*/ false);
        boundArgsValues = properties.filter((p) => p.enumerable).map((p) => p.value?.value);
    }

    return { targetFunctionText, boundThisValue, boundArgsValues };
}

View on GitHub (pinned to 793f7b2e16)

Solutions

  1. Bind the function before serializing: pass `func.bind(null, ...capturedValues)` instead of the raw function.
  2. Verify the value passed to the serializer is actually a function, not a wrapper object or proxy.
  3. Check that the Node/V8 version in use still reports [[TargetFunction]] via the inspector protocol; align with a supported runtime version.

Example fix

// before
serializeFunction(myHandler);
// after
serializeFunction(myHandler.bind(null, configValue));
Defensive patterns

Strategy: type-guard

Validate before calling

// before serializing
if (typeof func !== "function") throw new TypeError("expected a function");

Type guard

function isBoundFunction(f: unknown): f is Function {
  return typeof f === "function" && f.name.startsWith("bound ");
}

Try / catch

try {
  await serializeFunction(fn);
} catch (err) {
  if ((err as Error).message === "function is not a bound function") {
    fn = fn.bind(null, ...captured);
    await serializeFunction(fn);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling serializeFunction/captureOutputAsync (directly or via exports in a Pulumi program) with a function whose runtime descriptor is retrieved via getRuntimeIdForFunctionAsync but which is not created with `.bind()` — e.g. passing a plain arrow function or regular function where the serializer expected `fn.bind(null, ...)` with captured args.

Common situations: Custom closure serialization in provider/plugin code; upgrading the Node runtime or enabling V8 flags that change internal property reporting; hand-written code that mimics the SDK's bound-function capture pattern but passes an unbound function.

Related errors


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