{"record":{"id":"904270df93fad83f","repo":"withastro/astro","slug":"actionnotfounderror","errorCode":"ActionNotFoundError","errorMessage":"The server received a request for an action named `${actionName}` but could not find a match. If you renamed an action, check that you've updated your `actions/index` file and your calling code to match.","messagePattern":"The server received a request for an action named `(.+?)` but could not find a match\\. If you renamed an action, check that you've updated your `actions/index` file and your calling code to match\\.","errorType":"exception","errorClass":"AstroError","httpStatus":null,"severity":"error","filePath":"packages/astro/src/actions/load.ts","lineNumber":45,"sourceCode":"/** Looks up a single action handler by its dot-separated path. */\nexport async function getAction(\n\tmanifest: SSRManifest,\n\tpath: string,\n): Promise<ActionClient<unknown, ActionAccept, $ZodType>> {\n\tconst pathKeys = path.split('.').map((key) => decodeURIComponent(key));\n\tlet { server } = await getActions(manifest);\n\n\tif (!server || !(typeof server === 'object')) {\n\t\tthrow new TypeError(\n\t\t\t`Expected \\`server\\` export in actions file to be an object. Received ${typeof server}.`,\n\t\t);\n\t}\n\n\tfor (const key of pathKeys) {\n\t\t// An action is a leaf: once resolved to a function, its own properties\n\t\t// are not part of the action namespace and cannot be traversed further.\n\t\tif (typeof server === 'function') {\n\t\t\tthrow new AstroError({\n\t\t\t\t...ActionNotFoundError,\n\t\t\t\tmessage: ActionNotFoundError.message(pathKeys.join('.')),\n\t\t\t});\n\t\t}\n\t\tif (FORBIDDEN_PATH_KEYS.has(key)) {\n\t\t\tthrow new AstroError({\n\t\t\t\t...ActionNotFoundError,\n\t\t\t\tmessage: ActionNotFoundError.message(pathKeys.join('.')),\n\t\t\t});\n\t\t}\n\t\tif (!Object.hasOwn(server, key)) {\n\t\t\tthrow new AstroError({\n\t\t\t\t...ActionNotFoundError,\n\t\t\t\tmessage: ActionNotFoundError.message(pathKeys.join('.')),\n\t\t\t});\n\t\t}\n\t\t// @ts-expect-error we are doing a recursion... it's ugly\n\t\tserver = server[key];","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/withastro/astro/blob/52e6c34790cc8ac4e69e6135ace06049867e5c4a/packages/astro/src/actions/load.ts#L27-L63","documentation":"getAction() walks the dot-separated action path through the `server` actions object. Once an intermediate segment resolves to a function, that function is a leaf action: its own properties are not part of the action namespace. Requesting anything deeper than a leaf (e.g. `logout.fetch`) makes traversal step into a function's internals, which Astro rejects with ActionNotFoundError instead of leaking function properties.","triggerScenarios":"A client or caller requests an action name with extra segments after a leaf action, e.g. POSTing actionName 'logout.clear' when 'logout' is already a single defineAction. Also stitching paths dynamically where a leaf name is accidentally prefixed to a sub-key, or double-joining with dots ('blog.like' + '.' + 'like').","commonSituations":"Renaming a nested folder action to a flat one while stale client code still calls the old nested path; typos adding a suffix after an action name; hand-built action-name strings in middleware or custom fetch wrappers.","solutions":["Compare the requested dotted path against your actions/index.ts exports and remove the extra trailing segments","If you intended nesting, wrap the leaf in an object namespace: move the action into a sub-object (folder or nested export) so the deeper key exists","Bust stale client bundles: hard-reload / rebuild after renaming actions"],"exampleFix":"// before — action is a leaf, caller adds a sub-key\nexport const logout = defineAction({ handler: () => 'ok' });\nawait actions['logout.session'](); // throws\n\n// after\nawait actions.logout();","handlingStrategy":"validation","validationCode":"// centralize action names and validate against them before calling\nconst ACTION_PATHS = ['blog.like', 'blog.share', 'logout'] as const;\nfunction isKnownActionPath(p: string): boolean {\n  return (ACTION_PATHS as readonly string[]).includes(p);\n}\nif (!isKnownActionPath(name)) return new Response('Unknown action', { status: 404 });\nawait actions[name as keyof typeof actions](input);","typeGuard":"type ActionPath = 'blog.like' | 'blog.share' | 'logout';\nfunction isActionPath(p: string): p is ActionPath {\n  return ['blog.like', 'blog.share', 'logout'].includes(p);\n}","tryCatchPattern":"try {\n  await actions[name](input);\n} catch (e) {\n  if (e instanceof AstroError && e.code === 'ActionNotFoundError') {\n    return new Response('Unknown action', { status: 404 });\n  }\n  throw e;\n}","preventionTips":["Use static dotted access (actions.blog.like()) so the compiler verifies every path segment","Never build action names from unvalidated user input","After renaming actions, grep for old names and redeploy client bundles in lockstep with the server"],"tags":["astro-actions","action-not-found","rpc-lookup","namespace"],"backgroundTag":"rpc-method-not-found","analyzedSha":"52e6c34790cc8ac4e69e6135ace06049867e5c4a","analyzedAt":"2026-08-18T18:48:03.901Z","contentChangedAt":"2026-08-18T18:48:03.901Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}