microsoft/TypeScript · error · TypeError

Cannot add initializers after decoration has completed

Error message

Cannot add initializers after decoration has completed

What it means

In `__esDecorate`, each decorator's context.addInitializer pushes into an extraInitializers array; once the decoration loop completes the helper sets `done = true`. Calling addInitializer afterwards throws, matching the spec rule that initializers must be registered synchronously during decoration.

Source

Thrown at src/compiler/factory/emitHelpers.ts:783

// ES Decorators Helpers
const esDecorateHelper: UnscopedEmitHelper = {
    name: "typescript:esDecorate",
    importName: "__esDecorate",
    scoped: false,
    priority: 2,
    text: `
        var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
            function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
            var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
            var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
            var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
            var _, done = false;
            for (var i = decorators.length - 1; i >= 0; i--) {
                var context = {};
                for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
                for (var p in contextIn.access) context.access[p] = contextIn.access[p];
                context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
                var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
                if (kind === "accessor") {
                    if (result === void 0) continue;
                    if (result === null || typeof result !== "object") throw new TypeError("Object expected");
                    if (_ = accept(result.get)) descriptor.get = _;
                    if (_ = accept(result.set)) descriptor.set = _;
                    if (_ = accept(result.init)) initializers.unshift(_);
                }
                else if (_ = accept(result)) {
                    if (kind === "field") initializers.unshift(_);
                    else descriptor[key] = _;
                }
            }
            if (target) Object.defineProperty(target, contextIn.name, descriptor);
            done = true;
        };`,
};

View on GitHub (pinned to b465fdbfe1)

Solutions

  1. Call context.addInitializer synchronously inside the decorator body.
  2. Do not store or capture the addInitializer function for deferred use.
  3. If you need lazy behavior, register a single function that does the deferred work when invoked.

Example fix

// before
function deco(v, ctx) {
  setTimeout(() => ctx.addInitializer(() => console.log("hi")), 0); // throws after done
}

// after
function deco(v, ctx) {
  ctx.addInitializer(() => console.log("hi"));   // register synchronously
}
Defensive patterns

Strategy: validation

Validate before calling

// Register initializers synchronously during decoration only.
function deco(value: unknown, ctx: ClassMemberDecoratorContext) {
  ctx.addInitializer(() => { /* ... */ });  // OK: synchronous, inside decorator
}

Prevention

When it happens

Trigger: A decorator captures context.addInitializer and invokes it after the decoration loop has finished — e.g. from a later microtask, an async callback, or from within a deferred initializer.

Common situations: Decorator that defers registration to setTimeout/Promise resolution; storing addInitializer for reuse; re-entering it from another decorator's later phase.

Related errors


AI-assisted analysis of microsoft/TypeScript@b465fdbfe1 (2026-08-12). Data as JSON: /api/errors/f35b1e86c524b83e. Report an issue: GitHub.