{"record":{"id":"88f1e4b40b9a2b7a","repo":"Stirling-Tools/Stirling-PDF","slug":"this-operation-endpointtocheck-is-not-availab","errorCode":null,"errorMessage":"This operation (${endpointToCheck}) is not available. It may require a self-hosted instance with additional features enabled.","messagePattern":"This operation \\((.+?)\\) is not available\\. It may require a self-hosted instance with additional features enabled\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/desktop/services/operationRouter.ts","lineNumber":233,"sourceCode":"          `[operationRouter] Endpoint ${endpointToCheck} supported locally: ${supportedLocally}`,\n        );\n\n        if (!supportedLocally) {\n          // Local backend doesn't support this - check if SaaS supports it\n          const supportedOnSaaS =\n            await endpointAvailabilityService.isEndpointSupportedOnSaaS(\n              endpointToCheck,\n            );\n          console.debug(\n            `[operationRouter] Endpoint ${endpointToCheck} supported on SaaS: ${supportedOnSaaS}`,\n          );\n\n          if (!supportedOnSaaS) {\n            // Neither local nor SaaS support this - throw error\n            console.error(\n              `[operationRouter] Endpoint ${endpointToCheck} not supported on local or SaaS backend`,\n            );\n            throw new Error(\n              `This operation (${endpointToCheck}) is not available. It may require a self-hosted instance with additional features enabled.`,\n            );\n          }\n\n          // SaaS supports it - route to SaaS backend\n          if (!STIRLING_SAAS_BACKEND_API_URL) {\n            console.error(\n              \"[operationRouter] VITE_SAAS_BACKEND_API_URL not configured\",\n            );\n            throw new Error(\n              \"Cloud processing is required for this tool but VITE_SAAS_BACKEND_API_URL is not configured. \" +\n                \"Please check your environment configuration.\",\n            );\n          }\n          console.debug(\n            `[operationRouter] Routing ${operation} to SaaS backend (not supported locally, but supported on SaaS)`,\n          );\n          return STIRLING_SAAS_BACKEND_API_URL.replace(/\\/$/, \"\");","sourceCodeStart":215,"sourceCodeEnd":251,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/desktop/services/operationRouter.ts#L215-L251","documentation":"Thrown by OperationRouter.getBaseUrlForOperation() in SaaS mode after a two-stage capability probe fails on BOTH backends. The router first asks endpointAvailabilityService.isEndpointSupportedLocally() (probes the bundled Tauri backend) and, on a miss, asks isEndpointSupportedOnSaaS() (probes the hosted SaaS backend). Only when both return false is the operation declared unsupported. The endpoint name passed in is extracted from the operation path (e.g. '/api/v1/misc/repair' -> 'repair') via extractEndpointName().","triggerScenarios":"SaaS mode + the operation is a tool endpoint (isToolEndpoint true) + the local bundled backend is healthy and online (backendUrl && backendHealthy) + isEndpointSupportedLocally(endpointToCheck, backendUrl) === false AND isEndpointSupportedOnSaaS(endpointToCheck) === false. Reachable only when the local backend is actually up, because a not-yet-ready backend skips the check and falls through to local routing.","commonSituations":"The operation requires a proprietary/self-hosted-only feature flag that is disabled in this SaaS build; the endpoint name extraction strips a path segment the capability registry does not recognise; the SaaS backend's capability list is stale or the local registry endpoint returns an unexpected shape so both probes parse as 'unsupported'; a brand-new tool whose endpoint was added to the frontend before either backend shipped support.","solutions":["Confirm the real endpoint name: log extractEndpointName(operation) and compare it against the entry each backend advertises in its capability/feature list.","If the feature is self-hosted-only, switch the app to self-hosted mode (or run a self-hosted instance with the feature enabled) instead of SaaS mode.","If the SaaS backend should support it, verify the SaaS capability source (isEndpointSupportedOnSaaS) is reachable and returns the endpoint in its supported list.","If the local bundled backend should support it, verify the local backend is the expected build/variant (ultra-lite strips endpoints) and its endpoint registry includes the operation.","As a last resort, disable the tool in the frontend tool registry until at least one backend supports it, so users never reach this throw."],"exampleFix":"// before — throw is the only outcome when neither backend supports it\nif (!supportedOnSaaS) {\n  throw new Error(`This operation (${endpointToCheck}) is not available...`);\n}\n\n// after — surface a typed, user-facing 'unsupported' result the UI can render\n// instead of an uncaught throw, so callers can show a disabled-state badge\nthrow new OperationUnsupportedError(endpointToCheck, { locally: supportedLocally, saas: supportedOnSaaS });","handlingStrategy":"try-catch","validationCode":"// Pre-check both backends before routing, so unsupported tools never throw\nimport { endpointAvailabilityService } from \"@app/desktop/services/endpointAvailabilityService\";\nimport { tauriBackendService } from \"@app/desktop/services/tauriBackendService\";\n\nasync function isOperationAvailable(endpointName: string): Promise<boolean> {\n  const localUrl = tauriBackendService.getBackendUrl();\n  const localOk = localUrl && tauriBackendService.isOnline\n    ? await endpointAvailabilityService.isEndpointSupportedLocally(endpointName, localUrl)\n    : false;\n  if (localOk) return true;\n  return endpointAvailabilityService.isEndpointSupportedOnSaaS(endpointName);\n}","typeGuard":"export class OperationUnsupportedError extends Error {\n  constructor(public readonly endpoint: string, opts: { locally: boolean; saas: boolean }) {\n    super(`Operation ${endpoint} not available (local=${opts.locally}, saas=${opts.saas})`);\n    this.name = \"OperationUnsupportedError\";\n  }\n}\nfunction isOperationUnsupportedError(e: unknown): e is OperationUnsupportedError {\n  return e instanceof Error && /is not available/.test(e.message);\n}","tryCatchPattern":"try {\n  const base = await router.getBaseUrlForOperation(operation);\n} catch (e) {\n  if (isOperationUnsupportedError(e)) { showToolDisabled(e.endpoint); return; }\n  throw e;\n}","preventionTips":["Probe capability with endpointAvailabilityService before enabling a tool in the UI.","Log extractEndpointName(operation) in dev to catch extraction mismatches early.","Keep the frontend tool registry in sync with the backend's advertised capability list."],"tags":["desktop","operation-routing","capability-check","saas","self-hosted"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}