Stirling-Tools/Stirling-PDF · error · Error

This operation (${endpointToCheck}) is not available. It may

Error message

This operation (${endpointToCheck}) is not available. It may require a self-hosted instance with additional features enabled.

What it means

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().

Source

Thrown at frontend/editor/src/desktop/services/operationRouter.ts:233

          `[operationRouter] Endpoint ${endpointToCheck} supported locally: ${supportedLocally}`,
        );

        if (!supportedLocally) {
          // Local backend doesn't support this - check if SaaS supports it
          const supportedOnSaaS =
            await endpointAvailabilityService.isEndpointSupportedOnSaaS(
              endpointToCheck,
            );
          console.debug(
            `[operationRouter] Endpoint ${endpointToCheck} supported on SaaS: ${supportedOnSaaS}`,
          );

          if (!supportedOnSaaS) {
            // Neither local nor SaaS support this - throw error
            console.error(
              `[operationRouter] Endpoint ${endpointToCheck} not supported on local or SaaS backend`,
            );
            throw new Error(
              `This operation (${endpointToCheck}) is not available. It may require a self-hosted instance with additional features enabled.`,
            );
          }

          // SaaS supports it - route to SaaS backend
          if (!STIRLING_SAAS_BACKEND_API_URL) {
            console.error(
              "[operationRouter] VITE_SAAS_BACKEND_API_URL not configured",
            );
            throw new Error(
              "Cloud processing is required for this tool but VITE_SAAS_BACKEND_API_URL is not configured. " +
                "Please check your environment configuration.",
            );
          }
          console.debug(
            `[operationRouter] Routing ${operation} to SaaS backend (not supported locally, but supported on SaaS)`,
          );
          return STIRLING_SAAS_BACKEND_API_URL.replace(/\/$/, "");

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Confirm the real endpoint name: log extractEndpointName(operation) and compare it against the entry each backend advertises in its capability/feature list.
  2. 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.
  3. If the SaaS backend should support it, verify the SaaS capability source (isEndpointSupportedOnSaaS) is reachable and returns the endpoint in its supported list.
  4. 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.
  5. 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.

Example fix

// before — throw is the only outcome when neither backend supports it
if (!supportedOnSaaS) {
  throw new Error(`This operation (${endpointToCheck}) is not available...`);
}

// after — surface a typed, user-facing 'unsupported' result the UI can render
// instead of an uncaught throw, so callers can show a disabled-state badge
throw new OperationUnsupportedError(endpointToCheck, { locally: supportedLocally, saas: supportedOnSaaS });
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check both backends before routing, so unsupported tools never throw
import { endpointAvailabilityService } from "@app/desktop/services/endpointAvailabilityService";
import { tauriBackendService } from "@app/desktop/services/tauriBackendService";

async function isOperationAvailable(endpointName: string): Promise<boolean> {
  const localUrl = tauriBackendService.getBackendUrl();
  const localOk = localUrl && tauriBackendService.isOnline
    ? await endpointAvailabilityService.isEndpointSupportedLocally(endpointName, localUrl)
    : false;
  if (localOk) return true;
  return endpointAvailabilityService.isEndpointSupportedOnSaaS(endpointName);
}

Type guard

export class OperationUnsupportedError extends Error {
  constructor(public readonly endpoint: string, opts: { locally: boolean; saas: boolean }) {
    super(`Operation ${endpoint} not available (local=${opts.locally}, saas=${opts.saas})`);
    this.name = "OperationUnsupportedError";
  }
}
function isOperationUnsupportedError(e: unknown): e is OperationUnsupportedError {
  return e instanceof Error && /is not available/.test(e.message);
}

Try / catch

try {
  const base = await router.getBaseUrlForOperation(operation);
} catch (e) {
  if (isOperationUnsupportedError(e)) { showToolDisabled(e.endpoint); return; }
  throw e;
}

Prevention

When it happens

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

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

Related errors


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