{"record":{"id":"9dbdd8e77dba9de1","repo":"Stirling-Tools/Stirling-PDF","slug":"your-stirling-pdf-server-is-offline-and-endpoin","errorCode":null,"errorMessage":"Your Stirling-PDF server is offline and \"{{endpoint}}\" is not available on the local backend.","messagePattern":"Your Stirling-PDF server is offline and \"(.+?)\\}\" is not available on the local backend\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/desktop/services/operationRouter.ts","lineNumber":281,"sourceCode":"    if (mode === \"selfhosted\" && operation && this.isToolEndpoint(operation)) {\n      const { status } = selfHostedServerMonitor.getSnapshot();\n      if (status === \"offline\") {\n        const endpointName = this.extractEndpointName(operation);\n        const localUrl = tauriBackendService.getBackendUrl();\n        if (localUrl) {\n          const supportedLocally =\n            await endpointAvailabilityService.isEndpointSupportedLocally(\n              endpointName,\n              localUrl,\n            );\n          if (supportedLocally) {\n            console.debug(\n              `[operationRouter] Self-hosted server offline, routing ${operation} to local backend`,\n            );\n            return localUrl.replace(/\\/$/, \"\");\n          }\n        }\n        throw new Error(\n          i18n.t(\n            \"selfHosted.offline.toolNotAvailableLocally\",\n            'Your Stirling-PDF server is offline and \"{{endpoint}}\" is not available on the local backend.',\n            { endpoint: endpointName },\n          ),\n        );\n      }\n    }\n\n    // Existing logic for local/remote routing\n    const target = await this.getExecutionTarget(operation);\n\n    if (target === \"local\") {\n      // Use dynamically assigned port from backend service\n      const backendUrl = tauriBackendService.getBackendUrl();\n      if (!backendUrl) {\n        throw new Error(\n          \"Backend URL not available - backend may still be starting\",","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/desktop/services/operationRouter.ts#L263-L299","documentation":"Thrown in self-hosted mode when the remote Stirling server is offline and the requested tool endpoint is not supported by the local bundled backend either. Unlike the SaaS-mode equivalent, the message is i18n-translated via i18n.t with key 'selfHosted.offline.toolNotAvailableLocally' and an {endpoint} interpolation. It is the terminal branch of the self-hosted offline fallback: the router prefers the local backend, but if the local backend cannot serve the endpoint it gives up.","triggerScenarios":"mode === 'selfhosted' + operation is a tool endpoint + selfHostedServerMonitor snapshot status === 'offline' + localUrl is present (getBackendUrl non-null) + isEndpointSupportedLocally(endpointName, localUrl) === false. Also thrown when localUrl itself is null (no local backend port yet) while the server is offline.","commonSituations":"The user's remote Stirling server is down (network outage, server crash) and they tried a tool the local ultra-lite backend does not include; the local backend is a lite variant missing the operation; the server went offline mid-session and the capability probe against the local backend returns a false negative because the local backend is still booting.","solutions":["Restore the remote Stirling server (it is the authoritative source for this operation); the router will route to it once status returns to 'online'.","Run a fuller local backend variant (standard instead of ultra-lite) that includes the endpoint.","Confirm the local backend is actually healthy before concluding it cannot serve the endpoint — a still-booting local backend can answer capability probes incorrectly.","Gate the tool in the UI on server online state so the user cannot invoke an unsupported tool while offline."],"exampleFix":"// before — throws when localUrl is null OR local probe fails\nif (localUrl) {\n  const supportedLocally = await endpointAvailabilityService.isEndpointSupportedLocally(endpointName, localUrl);\n  if (supportedLocally) return localUrl.replace(/\\/$/, \"\");\n}\nthrow new Error(i18n.t(...));\n\n// after — distinguish 'no local backend yet' (transient) from 'genuinely unsupported'\nif (!localUrl) throw new BackendNotReadyError();\nif (!supportedLocally) throw new OperationUnavailableOfflineError(endpointName);","handlingStrategy":"validation","validationCode":"import { selfHostedServerMonitor } from \"@app/desktop/services/selfHostedServerMonitor\";\nimport { tauriBackendService } from \"@app/desktop/services/tauriBackendService\";\nimport { endpointAvailabilityService } from \"@app/desktop/services/endpointAvailabilityService\";\n\nasync function canRunOffline(endpointName: string): Promise<boolean> {\n  if (selfHostedServerMonitor.getSnapshot().status !== \"offline\") return true;\n  const localUrl = tauriBackendService.getBackendUrl();\n  if (!localUrl) return false;\n  return endpointAvailabilityService.isEndpointSupportedLocally(endpointName, localUrl);\n}","typeGuard":"function isOfflineToolUnavailable(e: unknown): boolean {\n  return e instanceof Error && /server is offline/.test(e.message);\n}","tryCatchPattern":"try {\n  const base = await router.getBaseUrlForOperation(operation);\n} catch (e) {\n  if (isOfflineToolUnavailable(e)) { showOfflineUnavailableNotice(); return; }\n  throw e;\n}","preventionTips":["Disable tools the local backend cannot serve while the server is offline.","Subscribe to selfHostedServerMonitor to update tool availability reactively.","Distinguish 'no local backend yet' (transient) from 'unsupported' before throwing."],"tags":["desktop","operation-routing","self-hosted","offline","i18n"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}