denoland/deno · error · Error

startActiveSpan requires a function argument

Error message

startActiveSpan requires a function argument

What it means

Thrown by Tracer.startActiveSpan() in Deno's built-in OpenTelemetry shim (ext/telemetry/telemetry.ts:374). The method resolves its argument overloads (name, fn), (name, options, fn), or (name, options, context, fn); if none of the second, third, or fourth argument is a function, it throws this Error. This mirrors @opentelemetry/api semantics where the span is only active for the duration of the synchronous callback.

Source

Thrown at ext/telemetry/telemetry.ts:396

    optionsOrFn: SpanOptions | F,
    fnOrContext?: F | Context,
    maybeFn?: F,
  ) {
    let options;
    let context;
    let fn;
    if (typeof optionsOrFn === "function") {
      options = undefined;
      fn = optionsOrFn;
    } else if (typeof fnOrContext === "function") {
      options = optionsOrFn;
      fn = fnOrContext;
    } else if (typeof maybeFn === "function") {
      options = optionsOrFn;
      context = fnOrContext;
      fn = maybeFn;
    } else {
      throw new Error("startActiveSpan requires a function argument");
    }
    if (options?.root) {
      context = ROOT_CONTEXT;
    } else {
      context = context ?? CURRENT.get() ?? ROOT_CONTEXT;
    }
    const span = this.startSpan(name, options, context);
    const ctx = CURRENT.enter(context.setValue(SPAN_KEY, span));
    try {
      return ReflectApply(fn, undefined, [span]);
    } finally {
      setAsyncContext(ctx);
    }
  }

  startSpan(name: string, options?: SpanOptions, context?: Context): Span {
    if (options?.root) {
      context = undefined;

View on GitHub (pinned to a961cdec3b)

Solutions

  1. Pass the callback as the second argument: tracer.startActiveSpan(name, (span) => { ... })
  2. If you need options, put them before the callback: tracer.startActiveSpan(name, { attributes }, (span) => { ... })
  3. If the callback comes from a variable, guard with typeof fn === 'function' before calling
  4. If you do not want an active-context callback, use tracer.startSpan(name, options, context) and end the span yourself

Example fix

// before
tracer.startActiveSpan("fetch", { attributes: { url } });

// after
tracer.startActiveSpan("fetch", { attributes: { url } }, (span) => {
  try {
    return doFetch(url);
  } finally {
    span.end();
  }
});
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof fn !== "function") {
  throw new TypeError("startActiveSpan requires a function argument");
}
tracer.startActiveSpan(name, options, fn);

Type guard

function isSpanCallback(
  v: unknown,
): v is (span: unknown) => unknown {
  return typeof v === "function";
}

Try / catch

try {
  tracer.startActiveSpan(name, options, fn);
} catch (e) {
  if (e instanceof Error && e.message.includes("requires a function argument")) {
    // fall back to a manual span
    const span = tracer.startSpan(name, options);
    span.end();
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: tracer.startActiveSpan('fetch') with no callback; tracer.startActiveSpan('fetch', { attributes: {...} }) with options but the callback forgotten; the fn variable being conditionally undefined at the call site (e.g. fn loaded from config); swapping the context and fn arguments so no checked position holds a function.

Common situations: Porting manual span code written with startSpan to startActiveSpan and forgetting the callback; refactors that extract the inline closure into a variable that can be undefined; JS code without TypeScript overloads catching the mistake at compile time.

Related errors


AI-assisted analysis of denoland/deno@a961cdec3b (2026-08-20). Data as JSON: /api/errors/6707a461fad3c471. Report an issue: GitHub.