apify/crawlee · error · Error
Cannot set() a borrowed OwnedOrInjected value
Error message
Cannot set() a borrowed OwnedOrInjected value
What it means
set() fills an owned, not-yet-filled OwnedOrInjected slot with the crawler-built default. Borrowed (injected) slots are never replaced — the injected instance's lifecycle belongs to the caller — so calling set() on one throws.
Source
Thrown at packages/core/src/owned_or_injected.ts:92
}
/**
* The resolved instance, or `undefined` when a lazily-filled owned slot hasn't been built yet. The non-throwing
* 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) {View on GitHub (pinned to dbe57fb09c)
Solutions
- Do not call set() when the user injected an instance; check `#owned`/ownership first or read the injected value instead.
- Skip the default-creation step when `maybeValue` already holds an injected instance.
- If you need a replaceable slot, construct the OwnedOrInjected as owned rather than borrowing an injected instance.
Example fix
// before this.clientSlot.set(createDefaultClient()); // throws when user injected one // after if (this.clientSlot.maybeValue == null) this.clientSlot.set(createDefaultClient());
Defensive patterns
Strategy: type-guard
Validate before calling
if (slot.maybeValue != null || !slotIsOwned(slot)) {
// skip set(): injected or already filled
} else {
slot.set(buildDefault());
} Try / catch
try {
slot.set(defaultValue);
} catch (err) {
if ((err as Error).message === 'Cannot set() a borrowed OwnedOrInjected value') {
// user injected their own instance; use it
return slot.value;
}
throw err;
} Prevention
- Always check maybeValue before attempting set().
- Treat injected instances as immutable — never overwrite them.
- Gate lazy-default creation behind an ownership check.
- Document which slots accept user injection.
When it happens
Trigger: Calling slot.set(defaultInstance) on a slot that was constructed in 'borrowed' mode because the user injected their own instance through options.
Common situations: Library-internal lazy default creation running even though the user supplied a custom instance; custom code that obtains a slot from a configured crawler and tries to overwrite the injected dependency.
Related errors
- OwnedOrInjected value is not initialized yet
- OwnedOrInjected value is already initialized
- Recoverable state has not yet been loaded - call initialize(
- ServiceConflictError('Configuration', configuration, this.#c
- ServiceConflictError('EventManager', eventManager, this.#eve
AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30).
Data as JSON: /api/errors/f4bf6c71911a616f.
Report an issue: GitHub.