Skip to content

Server Action passed as an argument fails when called after navigation #96331

Open

Description

@hi-ogawa

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 install and pnpm dev.
  • Open /a and select Save action A. This retains actionA in 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 actionA directly after navigating to page B succeeds, which confirms that the reference remains valid and normal stale-action forwarding works.
  • Calling outerAction(innerAction) on /nested succeeds 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/A

Which 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:

  1. The explicit request header identifies only the outer actionB.
  2. Next.js sees that page B can execute actionB, so it does not forward the request.
  3. decodeReply() then attempts to revive the serialized actionA argument through page B's server module map.
  4. Page B has no worker entry for actionA, so decoding fails before actionB executes.

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

  1. added theissue type on Jul 29, 2026
  2. BabuBahir commented on Jul 29, 2026

    @BabuBahir

    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 ?

    Image
  3. icyJoseph commented on Jul 30, 2026

    @icyJoseph
    Member

    This is somewhat expected, and it has to do with how server actions are bundled.

    Route /a has knowledge about server action A, and /b has 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 /b reached 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 /b works (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, /b has 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.

  4. self-assigned this
    on Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions