{"record":{"id":"6be014cf129193c0","repo":"Stirling-Tools/Stirling-PDF","slug":"this-operation-has-no-backend-endpoint-and-cannot","errorCode":null,"errorMessage":"This operation has no backend endpoint and cannot be executed directly.","messagePattern":"This operation has no backend endpoint and cannot be executed directly\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/core/utils/automationExecutor.ts","lineNumber":95,"sourceCode":"};\n\n/**\n * Execute single-file tool operation (processes files one at a time)\n */\nconst executeSingleFileOperation = async (\n  config: SingleFileToolOperationConfig<ErasedToolParams>,\n  parameters: ErasedToolParams,\n  files: File[],\n  filePrefix: string,\n): Promise<File[]> => {\n  const resultFiles: File[] = [];\n\n  const endpoint =\n    typeof config.endpoint === \"function\"\n      ? config.endpoint(parameters)\n      : config.endpoint;\n  if (!endpoint) {\n    throw new Error(\n      \"This operation has no backend endpoint and cannot be executed directly.\",\n    );\n  }\n\n  for (const file of files) {\n    const formData = config.buildFormData(parameters, file);\n\n    const processedFiles = await executeApiRequest(\n      endpoint,\n      formData,\n      [file],\n      filePrefix,\n      config.preserveBackendFilename,\n    );\n    resultFiles.push(...processedFiles);\n  }\n\n  return resultFiles;","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/core/utils/automationExecutor.ts#L77-L113","documentation":"Thrown inside executeSingleFileOperation when a tool's operationConfig.endpoint resolves to a falsy value. The endpoint can be a static string or a function of the parameters; if either yields null/undefined/empty-string, the operation cannot make an HTTP POST to the backend, so execution aborts. This means the tool is registered and has an operationConfig, but that config lacks a usable endpoint.","triggerScenarios":"executeToolOperationWithPrefix routes to the single-file branch for a tool whose operationConfig has endpoint: undefined, endpoint: null, endpoint: '' (empty string), or a dynamic endpoint function that returns undefined for the given parameters. Only single-file tools (those processed via executeSingleFileOperation) hit line 95.","commonSituations":"A tool was registered with only a customProcessor or with endpoint intentionally omitted because it is a frontend-only/display operation, but the automation executor was invoked on it anyway. A dynamic endpoint() function has a code path that returns undefined for an unhandled parameter combination. A tool was added to the registry skeleton but its endpoint was never wired.","solutions":["Check the tool's entry in toolsTaxonomy (the ToolRegistry) and confirm operationConfig.endpoint is set to a valid backend path (e.g. '/api/v1/general/split-pages').","If endpoint is a function, audit every parameter branch to ensure it always returns a non-empty string.","If the tool is frontend-only by design, exclude it from the automation operation picker so it can never be routed to the executor.","Gate the operation with a check (e.g. in the automation builder) that hides tools whose endpoint resolves to falsy."],"exampleFix":"// before\nendpoint: (params) => params.mode === 'x' ? '/api/v1/x' : undefined\n// after\nendpoint: (params) => params.mode === 'x' ? '/api/v1/x' : '/api/v1/default'","handlingStrategy":"validation","validationCode":"function hasUsableEndpoint(config: any, params: any): boolean {\n  const ep = typeof config.endpoint === 'function' ? config.endpoint(params) : config.endpoint;\n  return typeof ep === 'string' && ep.length > 0;\n}\n\nconst config = toolRegistry[op]?.operationConfig;\nif (!hasUsableEndpoint(config, params)) {\n  throw new Error(`Tool '${op}' cannot be executed (no endpoint).`);\n}","typeGuard":"function isExecutableSingleFileConfig(config: any, params: any): boolean {\n  if (!config || !config.buildFormData) return false;\n  const ep = typeof config.endpoint === 'function' ? config.endpoint(params) : config.endpoint;\n  return typeof ep === 'string' && ep.length > 0;\n}","tryCatchPattern":"try {\n  await executeSingleFileOperation(config, params, files, prefix);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('no backend endpoint')) {\n    showUser(`This tool has no direct backend endpoint and cannot run in automation.`);\n  } else { throw e; }\n}","preventionTips":["Maintain a registry test asserting every executable tool has a non-empty endpoint.","Exclude display-only/frontend-only tools from the automation operation list.","For dynamic endpoints, ensure all parameter branches return a valid string.","Log the resolved endpoint before execution during development."],"tags":["automation","tool-registry","endpoint","validation"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}