apify/crawlee · error

${operation} cannot be used inside a storage transaction: ${

Error message

${operation} cannot be used inside a storage transaction: ${reason} If you really need it, wrap the call in withDirectStorageAccess(() => ...) - operations performed there are applied immediately and are not rolled back.

What it means

`rejectOperationInTransaction(operation, reason)` is a shared guard that checks whether a storage transaction is currently active (via `activeStorageTransaction()`); if one is, it throws the standard 'cannot be used inside a storage transaction' error built by `operationRejectedInTransaction`, otherwise it returns silently. It protects operations that cannot be rolled back — RequestQueue operations like `drop`, `clearCache`, `fetchNextRequest`, `markRequestAsHandled`, `reclaimRequest`, and `purge` — because mutating them inside a transaction would survive a rollback or break the commit replay.

Source

Thrown at packages/core/src/storages/transaction.ts:429

export function snapshotValue<T>(value: T): T {
    try {
        return structuredClone(value);
    } catch {
        return JSON.parse(JSON.stringify(value));
    }
}

/**
 * The guard for operations that cannot be performed inside a storage transaction: throws when one is
 * active, and performs the per-operation cancellation check either way.
 * @internal
 */
export function rejectOperationInTransaction(operation: string, reason = 'it cannot be rolled back.'): void {
    if (activeStorageTransaction() === undefined) {
        return;
    }

    throw operationRejectedInTransaction(operation, reason);
}

/**
 * Builds the "operation not allowed in a transaction" error, for a call site that has already
 * established a transaction is active and so wants to `throw` unconditionally.
 * @internal
 */
export function operationRejectedInTransaction(operation: string, reason = 'it cannot be rolled back.'): Error {
    return new Error(
        `${operation} cannot be used inside a storage transaction: ${reason} ` +
            'If you really need it, wrap the call in withDirectStorageAccess(() => ...) - operations ' +
            'performed there are applied immediately and are not rolled back.',
    );
}

View on GitHub (pinned to dbe57fb09c)

Solutions

  1. Wrap the guarded call in `withDirectStorageAccess(() => ...)` if you explicitly want it applied immediately and never rolled back.
  2. Restructure the code so queue mutations happen outside the transaction — e.g. drop/purge the queue before opening the transaction or after it commits.
  3. If the mutation should be transactional, replace it with an equivalent operation that the transaction API supports (e.g. `addRequest` instead of `drop`/`purge` side effects).

Example fix

// before
await useStorageTransaction(async () => {
    await requestQueue.drop(); // throws
});

// after
await withDirectStorageAccess(() => requestQueue.drop());
Defensive patterns

Strategy: validation

Validate before calling

import { activeStorageTransaction } from '@crawlee/core/storages/transaction';
// Guard before mutating the request queue
if (activeStorageTransaction() !== undefined) {
  await withDirectStorageAccess(() => requestQueue.purge());
} else {
  await requestQueue.purge();
}

Type guard

function isInsideStorageTransaction(): boolean {
  return activeStorageTransaction() !== undefined;
}

Try / catch

try {
  await requestQueue.drop();
} catch (err) {
  if (String(err.message).includes('cannot be used inside a storage transaction')) {
    await withDirectStorageAccess(() => requestQueue.drop());
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling any guarded RequestQueue method (`drop()`, `purge()`, `reclaimRequest()`, `fetchNextRequest()`, `markRequestAsHandled()`, `clearCache()`) from within an active storage transaction opened with the transactions API; the guard detects `activeStorageTransaction() !== undefined` and throws.

Common situations: Crawler/queue maintenance code (purging a queue before a run, dropping a finished queue) accidentally executed inside a transactional callback that also persists state; custom request-handling logic calling `markRequestAsHandled` or `reclaimRequest` inside `useStorageTransaction`; migrating older code to the transaction-aware persistence layer.

Related errors


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