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
- Wrap the guarded call in `withDirectStorageAccess(() => ...)` if you explicitly want it applied immediately and never rolled back.
- Restructure the code so queue mutations happen outside the transaction — e.g. drop/purge the queue before opening the transaction or after it commits.
- 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
- Treat RequestQueue mutations (drop, purge, reclaimRequest, markRequestAsHandled, fetchNextRequest, clearCache) as non-transactional: always call them outside transaction blocks.
- Perform queue lifecycle operations (drop/purge) before opening or after committing a transaction.
- Use `withDirectStorageAccess` deliberately when you truly need immediate, non-rollbackable queue mutations.
- Add a lint/test check that no guarded queue methods are called inside `useStorageTransaction` callbacks.
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
- Request ID does not match its uniqueKey.
- Cannot record a journal entry on a transaction in the '${thi
- ${operation} cannot be used inside a storage transaction: ${
- The `requestManager` option cannot be used in conjunction wi
- ServiceConflictError('StorageBackend', storageBackend, this.
AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30).
Data as JSON: /api/errors/c278184c5a11bbed.
Report an issue: GitHub.