Failed to find Server Action "x": what that log line means
Failed to find Server Action "x" is what an automated React2Shell probe leaves in your logs: scanners send the header Next-Action: x, and no real action has that ID. The line also tells you something about your own app. Every 15.x and 16.x release since 21 July 2026 rejects an ID like "x" with a different message, so if your current build prints this one, you're running a next version below today's security floor. Upgrade it.
On recent 15.x and 16.x releases the full line looks like this:
Failed to find Server Action "x". This request might be from an older or newer deployment.
Read more: https://nextjs.org/docs/messages/failed-to-find-server-action
Older releases (14.0.0, 14.2.30 and 15.0.0 through 15.2.0, for example) end the message with an Original error: suffix instead of the link. In discussion #87851 that suffix reads Cannot read properties of undefined (reading 'workers'). That discussion opened on 28 December 2025, reports in it span 14.2.x through 16.1.1, and it has no maintainer answer.
Is "Failed to find Server Action x" an attack?
It's a probe, not proof of a breach. The open-source assetnote/react2shell-scanner sends "Next-Action": "x" in its default request headers, with a User-Agent ending in Assetnote/1.0.0. It's built to detect CVE-2025-55182 and CVE-2025-66478, the December 2025 React2Shell RCE. Because the probe targets the framework, not your code, the "x" also shows up on apps that never defined a Server Action.
Nobody wrote an action called x. A real action ID is a long, build-generated string. A one-character ID can't come from your client bundle, so a request carrying it was crafted by hand or by a tool.
What the log line doesn't tell you is whether the probe found anything. It records a failed action lookup, nothing more. If the app was running a Next.js version affected by React2Shell while these requests arrived, assume the worst and follow that post's secret-rotation order, starting with the Supabase service role key.
Why the exact line proves you're unpatched
Newer Next.js versions check the ID's shape before looking it up. next/dist/shared/lib/server-reference-info.js defines SERVER_REFERENCE_ID_LENGTH = 42, and mightBeServerReferenceId(id) is a plain id.length === 42 check. We checked the published packages on unpkg:
| Version | Length gate |
|---|---|
| 14.2.30, 14.2.35, 15.0.0, 15.5.7, 15.5.20 | No |
| 16.0.0, 16.0.10, 16.1.1, 16.2.10 | No |
| 15.5.21 to 15.5.28, 16.2.11 (15.5.21 and 16.2.11 published 21 July 2026) | Yes |
| 16.3.0 and later, including 16.4.3 | Yes |
On a gated version, Next-Action: x never reaches the "Failed to find" path. In next@16.4.3 the server logs a warning instead:
The Server Reference ID did not match the expected format. Received "x".
The client gets HTTP 400, Invalid Server Action request. A well-formed 42-character ID that isn't in the build still logs Failed to find Server Action "…" and returns 409, Server Action unavailable.
Every ungated release is numbered below 15.5.21 or 16.2.11. Both are far below today's floor of 15.5.28 and 16.4.1, which the latest safe Next.js version tracks. If the build you're running right now logs Failed to find Server Action "x", its next is older than every 2026 security floor from July onward. Lines from before your last upgrade don't count. Check the timestamp.
One wrinkle: the 11 December 2025 patch releases and later (16.0.10, 15.2.8 and 14.2.35, for example) log the message without an ID on some code paths: Failed to find Server Action. This request might be.... That form says nothing about your version either way.
Scan the manifest, not the logs
Here's a package.json pinned to 16.1.1, one of the versions people report in #87851:
{
"name": "my-saas",
"private": true,
"dependencies": {
"next": "16.1.1",
"react": "19.2.3",
"react-dom": "19.2.3",
"@supabase/ssr": "^0.8.0",
"@supabase/supabase-js": "^2.89.0"
}
}
- Criticalpackage.json:5
Dependency with a known advisory
Upgrade to the patched version and run npm audit to confirm the advisory is resolved.
That's general/vulnerable-dependency at critical severity. It compares next against the current floor, so the finding names the October 2026 advisories rather than React2Shell. To be clear about the limits: no static scan sees the probe itself, which is runtime traffic in your logs. The scan's job is the other half, catching the outdated version that makes the log line possible. It reads the version written in package.json, so with a caret range, run npm ls next to see what resolved.
A critical finding fails the GuardLayer check under the default policy. That blocks a merge only if you make the check a required status check in branch protection.
How do I stop the log spam?
Upgrade first. On 16.4.1 or later (15.5.28 on 15.x), the same probes produce the "did not match the expected format" warning and a 400. You're no longer running a version with known RCEs, but the warnings keep coming.
Commenters in #87851 blocked short headers at the reverse proxy before the request reaches Node. They used a cutoff of 10 to 15 characters in Caddy, Traefik and nginx. In nginx that looks like:
# Real action IDs are long. Drop obviously fake ones early.
if ($http_next_action ~ "^.{1,15}$") {
return 400;
}
Keep it to a short-length threshold. Don't write a rule that only lets exactly 42 characters through: older releases such as 14.2.30 and 15.0.0 predate the current ID format, so an exact-length rule could break real actions on those apps.
A proxy rule cuts the noise. It isn't a security control. Your actions are still public POST endpoints that need their own auth check, and the blocked requests were never going to reach one anyway.
When the ID is real: version skew
If the logged ID is a long string instead of x, you're probably not looking at a scanner. The error doc attributes "Failed to find Server Action" to the client and server running different builds. That's the accepted answer in discussion #75175 too: "Sounds like Version Skew." Self-hosted apps across several instances need the same build output and the same NEXT_SERVER_ACTIONS_ENCRYPTION_KEY, set at build time as a base64 value of 16, 24 or 32 bytes. On Vercel, Skew Protection keeps the previous deployment's actions reachable.
A note on that doc: its line about "traffic generated by automated security scanning" sits under the other heading, "Server Reference ID did not match the expected format". The doc doesn't say "Failed to find" means scanners. The version history above is how you get from the "x" to a scanner.
One more reason to upgrade
GHSA-p3m5-59mp-jv5r (CVE-2026-94482, published to OSV on 22 October 2026, rated High) is a separate bug. Crafted requests to App Router apps with at least one Server Action can drive CPU high enough to block other requests in the process. It affects 13.0.0 through 15.5.27 and 16.0.0 through 16.4.0, and the advisory lists no workaround besides upgrading. It isn't what the "x" probe does, but any version old enough to log the "x" line is in its range.
FAQ
What does the "x" in "Failed to find Server Action x" mean?
It's the literal value of the Next-Action request header. Assetnote's open-source React2Shell scanner sends Next-Action: x by default, and no real action has that ID.
Was my app hacked if I see this error?
The line alone doesn't say. It means someone probed for React2Shell. If your next version was affected by React2Shell while those requests arrived, rotate your secrets, starting with the Supabase service role key.
Why do I get this error when my app has no Server Actions?
Because the request comes from a scanner, not your client. Next.js treats any POST with a Next-Action header as a possible action call and logs the failed lookup, whether or not you wrote a "use server" function.
Does setting NEXT_SERVER_ACTIONS_ENCRYPTION_KEY fix it? Not for "x". The key fixes skew between builds or instances, which shows up as real-looking IDs. Several commenters in #87851 set it and still saw the "x" lines.
Which Next.js versions log "Server Reference ID did not match the expected format" instead? 15.5.21 and later on 15.x, 16.2.11 and later on 16.2, and every 16.3.0+ release. Those versions check the ID's length before the lookup.
Should I block the Next-Action header at my proxy? Block short values (under 10-15 characters) if you want quieter logs. Never block the header outright, because every real Server Action call uses it.
Catch this before it ships — free
Scan your files for this and 35 other Next.js + Supabase issues, with the exact fix inline. Connect GitHub and GuardLayer checks every push too — free on public repos.
No signup, no card. Raw files aren’t kept; only the report is saved.