apify/crawlee · error · Error

OwnedOrInjected value is already initialized

Error message

OwnedOrInjected value is already initialized

What it means

set() fills an owned slot exactly once. Calling it a second time would silently orphan the previous instance (leaking its resources and lifecycle), so a re-set on an already-present slot throws.

Source

Thrown at packages/core/src/owned_or_injected.ts:96

     * counterpart to {@apilink OwnedOrInjected.value|`value`} — pairs naturally with `?? fallback` so callers can read
     * a possibly-empty slot without the `isPresent ? value : …` dance.
     */
    get maybeValue(): Injected | undefined {
        return this.#present ? (this.#value as Injected) : undefined;
    }

    /**
     * Fills the (owned) slot with the crawler-built default, returning it for convenience. Only valid on an owned,
     * not-yet-filled slot: borrowed instances are never replaced and an owned slot is filled exactly once (re-setting
     * would silently orphan the previous instance's lifecycle).
     */
    set(value: Owned): Owned {
        if (!this.#owned) {
            throw new Error('Cannot set() a borrowed OwnedOrInjected value');
        }

        if (this.#present) {
            throw new Error('OwnedOrInjected value is already initialized');
        }

        this.#value = value;
        this.#present = true;

        return value;
    }

    /**
     * Runs an owned-only lifecycle hook, invoked (with the value typed as the concrete `Owned`) only when the crawler
     * owns a present instance — a no-op for a borrowed instance or an owned-but-not-yet-built slot.
     */
    async ifOwned<R>(fn: (value: Owned) => R | Promise<R>): Promise<R | undefined> {
        if (!this.#owned || !this.#present) {
            return undefined;
        }

        return fn(this.#value as Owned);

View on GitHub (pinned to dbe57fb09c)

Solutions

  1. Guard with `if (slot.maybeValue == null) slot.set(x)` so set() runs only once.
  2. Memoize the default instance and reuse it instead of re-invoking the init path.
  3. Create a new OwnedOrInjected slot if you genuinely need a fresh lifecycle.
  4. If a different instance is needed, replace the whole owning object rather than re-setting the slot.

Example fix

// before
this.slot.set(buildClient());
// ...later, same slot
this.slot.set(buildClient()); // throws
// after
if (this.slot.maybeValue == null) this.slot.set(buildClient());
Defensive patterns

Strategy: validation

Validate before calling

if (slot.maybeValue == null) {
  slot.set(buildClient());
}

Type guard

function isUnfilled<TOwned, TInjected>(slot: OwnedOrInjected<TOwned, TInjected>): boolean {
  return slot.maybeValue == null;
}

Try / catch

try {
  slot.set(buildClient());
} catch (err) {
  if ((err as Error).message === 'OwnedOrInjected value is already initialized') {
    return slot.value; // reuse existing instance
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling set() twice on the same owned slot — e.g. lazy-init logic running twice (double crawler init, re-run, or two code paths both calling set()), or calling set() after the slot was already filled.

Common situations: Re-running initialization in tests without recreating the crawler; concurrent/overlapping lazy-init paths; accidentally calling set() on a slot already populated by an earlier call.

Related errors


AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30). Data as JSON: /api/errors/cbed9cbdbe893864. Report an issue: GitHub.