Stirling-Tools/Stirling-PDF · error · Error

This operation has no backend endpoint and cannot be…

Error message

This operation has no backend endpoint and cannot be executed directly.

What it means

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.

Solutions

  1. 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').
  2. If endpoint is a function, audit every parameter branch to ensure it always returns a non-empty string.
  3. If the tool is frontend-only by design, exclude it from the automation operation picker so it can never be routed to the executor.
  4. Gate the operation with a check (e.g. in the automation builder) that hides tools whose endpoint resolves to falsy.

Example fix

// before
endpoint: (params) => params.mode === 'x' ? '/api/v1/x' : undefined
// after
endpoint: (params) => params.mode === 'x' ? '/api/v1/x' : '/api/v1/default'
Defensive patterns

Strategy: validation

Validate before calling

function hasUsableEndpoint(config: any, params: any): boolean {
  const ep = typeof config.endpoint === 'function' ? config.endpoint(params) : config.endpoint;
  return typeof ep === 'string' && ep.length > 0;
}

const config = toolRegistry[op]?.operationConfig;
if (!hasUsableEndpoint(config, params)) {
  throw new Error(`Tool '${op}' cannot be executed (no endpoint).`);
}

Type guard

function isExecutableSingleFileConfig(config: any, params: any): boolean {
  if (!config || !config.buildFormData) return false;
  const ep = typeof config.endpoint === 'function' ? config.endpoint(params) : config.endpoint;
  return typeof ep === 'string' && ep.length > 0;
}

Try / catch

try {
  await executeSingleFileOperation(config, params, files, prefix);
} catch (e) {
  if (e instanceof Error && e.message.includes('no backend endpoint')) {
    showUser(`This tool has no direct backend endpoint and cannot run in automation.`);
  } else { throw e; }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13). Data as JSON: /api/errors/6be014cf129193c0. Report an issue: GitHub.

Appendix: source

Thrown at frontend/editor/src/core/utils/automationExecutor.ts:95

};

/**
 * Execute single-file tool operation (processes files one at a time)
 */
const executeSingleFileOperation = async (
  config: SingleFileToolOperationConfig<ErasedToolParams>,
  parameters: ErasedToolParams,
  files: File[],
  filePrefix: string,
): Promise<File[]> => {
  const resultFiles: File[] = [];

  const endpoint =
    typeof config.endpoint === "function"
      ? config.endpoint(parameters)
      : config.endpoint;
  if (!endpoint) {
    throw new Error(
      "This operation has no backend endpoint and cannot be executed directly.",
    );
  }

  for (const file of files) {
    const formData = config.buildFormData(parameters, file);

    const processedFiles = await executeApiRequest(
      endpoint,
      formData,
      [file],
      filePrefix,
      config.preserveBackendFilename,
    );
    resultFiles.push(...processedFiles);
  }

  return resultFiles;

View on GitHub (pinned to 9ef20dcab8)