Skip to content

Conversation

@logaretm
Copy link
Collaborator

@logaretm logaretm commented Jan 19, 2026

Summary

This was introduced by cache components workaround in #18700.

When deploying a Next.js app on Cloudflare Workers and I presume other serverless environments, you would get this error

NextJS request failed. Error: Cannot call this AsyncLocalStorage bound function outside of the request in which it was created.

This happens because the snapshot becomes stale in the next request invocation.

Solution

This is more of a best-effort workaround. I added a retry logic based on some criteria, if the first attempt fails due to ALS error then it will grab a new snapshot and re-run it, if that also fails, then it will run the callback directly.

the flow goes like this:

flowchart TD
    A[callback invoked] --> B{Try cached snapshot}
    B -->|Success| C[Return result]
    B -->|Error| D{Is AsyncLocalStorage error?}
    
    D -->|No| E[Rethrow error]
    D -->|Yes| F[Get fresh snapshot]
    
    F --> G{Fresh snapshot available?}
    G -->|No| H[Execute callback directly]
    G -->|Yes| I[Update cached snapshot]
    
    I --> J{Try fresh snapshot}
    J -->|Success| K[Return result]
    J -->|Error| L{Is AsyncLocalStorage error?}
    
    L -->|No| M[Rethrow error]
    L -->|Yes| N[Execute callback directly]
    
    H --> O[Return result]
    N --> O

    style C fill:#90EE90
    style K fill:#90EE90
    style O fill:#90EE90
    style E fill:#FFB6C1
    style M fill:#FFB6C1
Loading

Closes #18842

Copilot AI review requested due to automatic review settings January 19, 2026 14:43
@linear
Copy link

linear bot commented Jan 19, 2026

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds fallback logic for AsyncLocalStorage (ALS) snapshots in serverless environments like Cloudflare Workers. The issue occurs when ALS snapshots become stale after the request context in which they were created ends, causing "Cannot call this AsyncLocalStorage bound function outside of the request" errors.

Changes:

  • Replaced direct snapshot assignment with a wrapper function that implements retry logic with fallback
  • Added helper functions getAsyncLocalStorageSnapshot() and isAsyncLocalStorageError() to modularize snapshot creation and error detection
  • Renamed type alias from _INTERNAL_RandomSafeContextRunner to RandomSafeContextRunner for cleaner local naming

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}

function isAsyncLocalStorageError(error: unknown): boolean {
return error instanceof Error && error.message.includes('AsyncLocalStorage');
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error detection logic is too broad and may incorrectly catch errors that merely mention "AsyncLocalStorage" in their message but aren't the specific ALS binding error that needs to be handled. Consider checking for the exact error message: "Cannot call this AsyncLocalStorage bound function outside of the request in which it was created." This will prevent false positives where unrelated errors that happen to mention AsyncLocalStorage in their message would incorrectly trigger the retry/fallback logic.

Suggested change
return error instanceof Error && error.message.includes('AsyncLocalStorage');
return (
error instanceof Error &&
error.message ===
'Cannot call this AsyncLocalStorage bound function outside of the request in which it was created.'
);

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exact error matching is flimsy IMO, also I am not sure if it would be the exact same error in each serverless environment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also not sure what would be the best call here, I think just AsyncLocalStorage is fine when thrown at this point

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Copy link
Member

@chargome chargome left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updating! I guess there is no way to properly reflect this in a e2e test so lgtm!

throw error;
}

// Snapshot likely stale, try to get a fresh one and retry
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can the snapshot get stale?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps it's not the best way to describe this, but this was the closest analogy. This is what I found while debugging, when we set the ALS snapshot on startup:

  • if cold start it gets initialised again and it works as expected, at that point the snapshot isn't set, so the callbacks execute normally.
  • if warm start, the global already exists, so some random APIs might run before the init takes effect. It will run with the previous snapshot that was set which would be in a different request context.

Or that's what I think is happening.

}

function isAsyncLocalStorageError(error: unknown): boolean {
return error instanceof Error && error.message.includes('AsyncLocalStorage');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also not sure what would be the best call here, I think just AsyncLocalStorage is fine when thrown at this point

@logaretm logaretm merged commit 6c9317c into develop Jan 19, 2026
69 checks passed
@logaretm logaretm deleted the awad/js-1480-asynclocalstorage-nextjs-error-with-cloudflare-workers branch January 19, 2026 22:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AsyncLocalStorage Next.js Error with Cloudflare Workers

3 participants