Repository navigation
Server Action passed as an argument fails when called after navigation #96331
Description
Link to the code that reproduces this issue
https://github.com/hi-ogawa/reproductions/tree/main/next-rsc-action-argument
To Reproduce
The reproduction includes following two actions that are exclusively accessed through two pages through two client components:
// app/a/actions.ts
'use server'
export async function actionA() {
return 'ACTION_A_OK'
}
// app/b/actions.ts
'use server'
export async function actionB(action) {
return `ACTION_B_OK(${await action()})`
}The idea is that the client component in /a stashes actionA as client state, then after the navigation, the client component in /b triggers actionB(stashedAction). When actionA is called from page /b in that way, it leads to an error like:
⨯ Error: Failed to find Server Action "002e999fb6c51172959666feee27cb3ccda5ce5565". This request might be from an older or newer deployment.
Read more: https://nextjs.org/docs/messages/failed-to-find-server-action
at ignore-listed frames {
digest: '370130379@E974'
}
POST /b 500 in 149ms (next.js: 3ms, application-code: 146ms)Following is a step-by-step instruction:
- Run
pnpm installandpnpm dev. - Open
/aand select Save action A. This retainsactionAin shared client runtime state. - Navigate client-side to
/b. - Select Run saved action A directly and observe that it succeeds with
ACTION_A_OK. - Select Run action B with saved action A.
- Observe an error.
The automated reproduction is available through pnpm test.
Current vs. Expected behavior
Currently, calling actionB(savedActionA) on page B fails while decoding the arguments, before actionB executes. Page B statically reaches actionB, but it does not statically reach the retained actionA reference.
I expected the valid retained reference to be decoded and passed to actionB, producing:
ACTION_B_OK(ACTION_A_OK)
The reproduction includes two controls:
- Calling retained
actionAdirectly after navigating to page B succeeds, which confirms that the reference remains valid and normal stale-action forwarding works. - Calling
outerAction(innerAction)on/nestedsucceeds when the current page statically reaches both route-local action modules, which confirms that passing a server reference as an action argument is supported in that case.
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 25.5.0: Mon Apr 27 20:41:15 PDT 2026; root:xnu-12377.121.6~2/RELEASE_ARM64_T6041
Available memory (MB): 49152
Available CPU cores: 12
Binaries:
Node: 24.18.0
npm: 11.16.0
Yarn: 1.22.22
pnpm: 11.17.0
Relevant Packages:
next: 16.3.0-canary.102
eslint-config-next: N/A
react: 19.2.3
react-dom: 19.2.3
typescript: 5.9.3
Next.js Config:
output: N/AWhich area(s) are affected? (Select all that apply)
Server Actions
Which stage(s) are affected? (Select all that apply)
next dev (local), next build (local), next start (local)
Additional context
The result was verified with Next.js 16.3.0-canary.102 in all four combinations:
- Turbopack development;
- Turbopack production;
- Webpack development;
- Webpack production.
Both production server-reference-manifest.json files confirm that actionA has only app/a/page as a worker and actionB has only app/b/page as a worker.
The observed request flow appears to be:
- The explicit request header identifies only the outer
actionB. - Next.js sees that page B can execute
actionB, so it does not forward the request. decodeReply()then attempts to revive the serializedactionAargument through page B's server module map.- Page B has no worker entry for
actionA, so decoding fails beforeactionBexecutes.
Related issues #61918 and #63915 covered directly invoking an action after navigation. That case succeeds in this reproduction. Issue #78529 concerns re-exported actions under Turbopack, while this reproduction fails with both Turbopack and Webpack.
The first canary release containing this behavior has not been identified.
Side note: The actual context is that this reproduction was discovered while analyzing Next.js's action internal dispatching mechanism and exploring similar features on Vite rsc plugin. The discussion is found in vitejs/vite-plugin-react#1337
Activity
BabuBahir commentedon Jul 29, 2026
hi @hi-ogawa Yes , I can confirm that , Even I tested in my local with your "next-rsc-action-argument" folder , and was able to replicate this error ?
icyJoseph commentedon Jul 30, 2026
This is somewhat expected, and it has to do with how server actions are bundled.
Route
/ahas knowledge about server action A, and/bhas knowledge about action B.When you save the action in the client, you're actually saving a "proxied" function that carries a reference ID pointing at action A's implementation on the server.
When you invoke action B, you go through the same kind of proxy — a reference ID for B, plus the arguments, which in this case include A's proxy and its reference ID.
When the call is finally POST'd, the request header carries only B's reference ID, and that's what the server uses to pick where to find the implementation. That location won't necessarily know about A — it only would if
/breached A in its own source. The action then runs in a worker.Before B's body executes, the worker has to decode the arguments, and there it hits A's reference ID. That ID isn't in the map available to
/b, so decoding fails and we get the error — B never runs at all.Note that invoking the saved A from
/bworks (without wrapping it), because there the header is A's ID and the request gets forwarded to a route that can resolve it. Only the argument path is scoped to the current route.To make A's reference ID resolvable from
/b,/bhas to reach it through the very same module — a re-export through another"use server"file won't do it, since that mints a separate reference ID for the same function.I talked this over with a colleague and we think it's fixable — we should be able to resolve these references properly. Assigning to myself for now. I'll try to loop back soon.
We already look up the server action this way when A is invoked directly. The finding system just needs to be smarter about the argument path.