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
- Guard with `if (slot.maybeValue == null) slot.set(x)` so set() runs only once.
- Memoize the default instance and reuse it instead of re-invoking the init path.
- Create a new OwnedOrInjected slot if you genuinely need a fresh lifecycle.
- 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
- Guard every set() call with a maybeValue == null check.
- Make lazy-init idempotent (memoize the built instance).
- Avoid initializing the same crawler object twice; recreate it instead.
- In tests, build a fresh crawler per test rather than re-running init.
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
- Statistics.startCapturing() was already called - this instan
- OwnedOrInjected value is not initialized yet
- Cannot set() a borrowed OwnedOrInjected value
- Recoverable state has not yet been loaded - call initialize(
- RequestList sources are already loading or were loaded.
AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30).
Data as JSON: /api/errors/cbed9cbdbe893864.
Report an issue: GitHub.