BabylonJS/Babylon.js · error

At least one hook must be provided.

Error message

At least one hook must be provided.

What it means

InterceptFunction wraps an object's method so registered hooks run around calls, but it requires at least one hook (the guard checks hooks.afterCall) to be useful. If hooks.afterCall is missing the library throws immediately rather than installing a do-nothing interceptor. A second guard throws if the target property is not actually a function.

Source

Thrown at packages/dev/inspector-v2/src/instrumentation/functionInstrumentation.ts:34

/**
 * Intercepts a function on an object and allows you to add hooks that will be called during function execution.
 * @param target The object containing the function to intercept.
 * @param propertyKey The key of the property that is a function (this is the function that will be intercepted).
 * @param hooks The hooks to call during the function execution.
 * @returns A disposable that removes the hooks when disposed and returns the object to its original state.
 */
// This overload only matches when K is a specific literal key (not a union like keyof T)
export function InterceptFunction<T extends object, K extends keyof T>(
    target: T,
    propertyKey: string extends K ? never : number extends K ? never : symbol extends K ? never : K,
    hooks: NonNullable<T[K]> extends (...args: infer Args) => unknown ? FunctionHooks<Args> : FunctionHooks
): IDisposable;
// Fallback overload for generic/dynamic cases where the function type cannot be inferred
export function InterceptFunction<T extends object>(target: T, propertyKey: keyof T, hooks: FunctionHooks): IDisposable;
/** @internal */
export function InterceptFunction<T extends object>(target: T, propertyKey: keyof T, hooks: FunctionHooks): IDisposable {
    if (!hooks.afterCall) {
        throw new Error("At least one hook must be provided.");
    }

    const originalFunction = Reflect.get(target, propertyKey, target) as (...args: any) => any;
    if (typeof originalFunction !== "function") {
        throw new Error(`Property "${propertyKey.toString()}" of object "${target}" is not a function.`);
    }

    // Make sure the property is configurable and writable, otherwise it is immutable and cannot be intercepted.
    const propertyDescriptor = Reflect.getOwnPropertyDescriptor(target, propertyKey);
    if (propertyDescriptor) {
        if (!propertyDescriptor.configurable) {
            throw new Error(`Property "${propertyKey.toString()}" of object "${target}" is not configurable.`);
        }

        if (propertyDescriptor.writable === false || (propertyDescriptor.writable === undefined && !propertyDescriptor.set)) {
            throw new Error(`Property "${propertyKey.toString()}" of object "${target}" is readonly.`);
        }
    }

View on GitHub (pinned to 0592b347b8)

Solutions

  1. Provide at least an afterCall hook, e.g. InterceptFunction(target, "play", { afterCall: (r) => log(r) }).
  2. Ensure the hooks object is built with afterCall always present, even if the callback is a no-op.
  3. Confirm the property exists and is a function on the target to avoid the follow-up "is not a function" error.
  4. Type hooks as FunctionHooks so TypeScript rejects objects missing afterCall.

Example fix

// before
InterceptFunction(soundtrack, "play", {}); // throws

// after
InterceptFunction(soundtrack, "play", {
  afterCall: (result) => {
    console.log("play called, returned:", result);
    return result;
  }
});
Defensive patterns

Strategy: validation

Validate before calling

if (!hooks || typeof hooks.afterCall !== "function") {
  throw new TypeError("InterceptFunction requires an afterCall hook");
}
if (typeof target[propertyKey] !== "function") {
  throw new TypeError(`Property "${String(propertyKey)}" is not a function`);
}

Type guard

function hasValidHooks<T extends object>(t: T, k: keyof T, h: FunctionHooks): h is FunctionHooks & Required<Pick<FunctionHooks, "afterCall">> {
  return !!h && typeof h.afterCall === "function" && typeof t[k] === "function";
}

Try / catch

try {
  const disposable = InterceptFunction(target, "play", hooks);
  interceptors.push(disposable);
} catch (e) {
  if (e instanceof Error && (e.message.includes("hook must be provided") || e.message.includes("is not a function"))) {
    console.error("Interception setup failed:", e.message);
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling InterceptFunction(target, "method", {}) or with a hooks object lacking afterCall; constructing hooks dynamically and dropping the afterCall key; passing hooks typed loosely as any so an empty object slips through.

Common situations: Building instrumentation helpers where hooks are conditionally assembled and all conditions are false; refactoring that renamed afterCall (newer API shape) leaving an empty hooks object; copy-pasting an overload call with the wrong hooks shape.

Related errors


AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30). Data as JSON: /api/errors/f16635d093c8db48. Report an issue: GitHub.