{"record":{"id":"b85f7d38ed69f811","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/core/base-pipeline.ts","lineNumber":380,"sourceCode":"\t\t\tserverIslandNameMap: new Map(),\n\t\t};\n\t}\n\n\tasync getAction(path: string): Promise<ActionClient<unknown, ActionAccept, $ZodType>> {\n\t\tconst pathKeys = path.split('.').map((key) => decodeURIComponent(key));\n\t\tlet { server } = await this.getActions();\n\n\t\tif (!server || !(typeof server === 'object')) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`Expected \\`server\\` export in actions file to be an object. Received ${typeof server}.`,\n\t\t\t);\n\t\t}\n\n\t\tfor (const key of pathKeys) {\n\t\t\t// An action is a leaf: once resolved to a function, its own properties\n\t\t\t// are not part of the action namespace and cannot be traversed further.\n\t\t\tif (typeof server === 'function') {\n\t\t\t\tthrow new AstroError({\n\t\t\t\t\t...ActionNotFoundError,\n\t\t\t\t\tmessage: ActionNotFoundError.message(pathKeys.join('.')),\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (FORBIDDEN_PATH_KEYS.has(key)) {\n\t\t\t\tthrow new AstroError({\n\t\t\t\t\t...ActionNotFoundError,\n\t\t\t\t\tmessage: ActionNotFoundError.message(pathKeys.join('.')),\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (!Object.hasOwn(server, key)) {\n\t\t\t\tthrow new AstroError({\n\t\t\t\t\t...ActionNotFoundError,\n\t\t\t\t\tmessage: ActionNotFoundError.message(pathKeys.join('.')),\n\t\t\t\t});\n\t\t\t}\n\t\t\t// @ts-expect-error we are doing a recursion... it's ugly\n\t\t\tserver = server[key];","sourceCodeStart":362,"sourceCodeEnd":398,"githubUrl":"https://github.com/withastro/astro/blob/d081033d5fe8e8a68c4bbbad4af9d2deb9c74bca/packages/astro/src/core/base-pipeline.ts#L362-L398","documentation":"An `ActionNotFoundError` thrown during action path traversal when an intermediate path segment resolves to a function before all keys are consumed. Actions are leaf nodes — once the traversal reaches a function, it cannot go deeper. For example, requesting `foo.bar` when `foo` is already an action function triggers this. The guard is `if (typeof server === 'function')` inside the key loop.","triggerScenarios":"`getAction('parent.child')` is called. During traversal, `server` is reassigned to `server['parent']` which is a function. On the next iteration for `'child'`, the `typeof server === 'function'` check fires and `ActionNotFoundError` is thrown with the full dotted path.","commonSituations":"Calling a nested action path where the parent is a flat action (e.g. `Astro.callAction('login.google')` when `login` is itself a function). A client sends a fabricated action path. Renaming/refactoring actions creates a naming collision where a namespace name is also an action name.","solutions":["Check that the action path doesn't traverse through an existing leaf action name.","Rename the conflicting action or namespace so they don't overlap.","Run `astro check` to detect mismatched action names.","Ensure `Astro.callAction` uses the correct dotted path matching your `server` export structure."],"exampleFix":"// before — src/actions/index.ts\nexport const server = {\n  login: defineAction({ handler: async () => { ... } }), // leaf\n};\n// calling Astro.callAction('login.google') → error\n\n// after\nexport const server = {\n  login: {\n    password: defineAction({ handler: async () => { ... } }),\n    google: defineAction({ handler: async () => { ... } }),\n  },\n};","handlingStrategy":"validation","validationCode":"function isValidActionPath(server: object, path: string): boolean {\n  let current: unknown = server;\n  for (const key of path.split('.')) {\n    if (typeof current === 'function') return false; // hit a leaf too early\n    if (!Object.hasOwn(current as object, key)) return false;\n    current = (current as Record<string, unknown>)[key];\n  }\n  return typeof current === 'function';\n}","typeGuard":null,"tryCatchPattern":"try {\n  await Astro.callAction('users.delete', input);\n} catch (e) {\n  if (e instanceof Error && e.name === 'ActionNotFoundError') {\n    console.error('Action path invalid:', e.message);\n  }\n  throw e;\n}","preventionTips":["Keep action namespacing flat or clearly nested — don't mix leaf and branch names.","Use TypeScript types from astro:actions to get compile-time path checking.","Document the action tree structure for callers."],"tags":["actions","action-not-found","namespacing","traversal"],"backgroundTag":null,"analyzedSha":"d081033d5fe8e8a68c4bbbad4af9d2deb9c74bca","analyzedAt":"2026-08-12T13:37:29.035Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}