BabylonJS/Babylon.js · error

A service producing the contract '${contract.toString()}' ha

Error message

A service producing the contract '${contract.toString()}' has already been added to this '${this._friendlyName}' container.

What it means

ServiceContainer._addService rejects a service definition whose `produces` array contains a contract (symbol) already mapped in this container's _serviceDefinitions. The container enforces one-producer-per-contract per container so dependency resolution stays unambiguous. It is a registration-time guard, thrown synchronously from addServices.

Source

Thrown at packages/dev/sharedUiComponents/src/modularTool/modularity/serviceContainer.ts:106

    }

    /**
     * Registers a service definition in the service container.
     * @param serviceDefinition The service definition to register.
     * @returns A disposable that will remove the service definition from the service container.
     */
    public addService<Produces extends IService<symbol>[] = [], Consumes extends IService<symbol>[] = []>(serviceDefinition: ServiceDefinition<Produces, Consumes>): IDisposable {
        return this.addServices(serviceDefinition);
    }

    private _addService(service: WeaklyTypedServiceDefinition) {
        if (this._isDisposed) {
            throw new Error(`'${this._friendlyName}' container is disposed.`);
        }

        service.produces?.forEach((contract) => {
            if (this._serviceDefinitions.has(contract)) {
                throw new Error(`A service producing the contract '${contract.toString()}' has already been added to this '${this._friendlyName}' container.`);
            }
        });

        service.produces?.forEach((contract) => {
            this._serviceDefinitions.set(contract, service);
        });

        const dependencies = service.consumes?.map((contract) => this._resolveDependency(contract, service)) ?? [];

        this._serviceInstances.set(service, service.factory(...dependencies));
    }

    /**
     * Resolves a dependency by contract identity for a consuming service.
     * Checks local services first, then walks up the parent chain.
     * Registers the consumer as a dependent in whichever container owns the dependency.
     * @param contract The contract identity to resolve.
     * @param consumer The service definition that consumes this dependency.

View on GitHub (pinned to 0592b347b8)

Solutions

  1. Remove the duplicate addServices call or guard it with a check of which contracts are already registered before adding.
  2. If two services must share a contract, have only one produce it and the other consume it (list it in consumes, not produces).
  3. For child-container scenarios, add the second producer to a child container instead of the same container.
  4. Ensure contract symbols come from a single shared module so identity is unique and intentional.

Example fix

// before
container.addServices(myService); // myService.produces = [FooContract]
container.addServices(myService); // throws: contract already added
// after
if (!container.hasService?.(FooContract)) {
    container.addServices(myService);
}
Defensive patterns

Strategy: validation

Validate before calling

// assume a registry snapshot or helper exists; otherwise track locally
const registered = new Set();
function safeAdd(container, service) {
  if (service.produces?.some((c) => registered.has(c))) return; // skip duplicates
  container.addServices(service);
  service.produces?.forEach((c) => registered.add(c));
}

Try / catch

// try {
//   container.addServices(service);
// } catch (e) {
//   if (!String(e.message).includes("has already been added")) throw e;
//   // treat as idempotent re-registration
// }

Prevention

When it happens

Trigger: Calling container.addServices(service) where service.produces contains a contract symbol already produced by a previously added service in the same container; adding the same service instance twice; two modules declaring the same contract symbol (e.g. both export a symbol with the same registry key).

Common situations: Two plugin modules each exporting their own contract symbol intended to be 'the same' service; duplicate addServices calls after hot-reload or re-initialization of a modular tool extension; copy-pasting a service definition that already exists in the host container.

Related errors


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