paperclipai/paperclip · info

Upgrade Required

Error message

Upgrade Required

What it means

In static (non-dev) mode the server still must bind a placeholder listener on the derived Vite HMR port so the managed-runtime supervisor's readiness check (which waits for both ports) does not kill a healthy server (PAP-18043). That placeholder handler responds 426 Upgrade Required to any request because there is no Vite/HMR to serve in static mode.

Solutions

  1. Send requests to the main server port, not the derived Vite HMR port
  2. Update any proxy/monitor to target the application port; treat the HMR port as internal/supervisor-only
  3. If you need HMR, run in dev mode where a real Vite server binds that port

Example fix

// before
curl http://localhost:3101/  # derived HMR port in static mode -> 426
// after
curl http://localhost:3100/  # main app port
Defensive patterns

Strategy: type-guard

Validate before calling

const isMainAppPort = (port: number, serverPort: number) => port === serverPort;
if (!isMainAppPort(targetPort, configuredServerPort)) console.warn("target is the placeholder HMR port; expect 426 in static mode");

Type guard

const isUpgradeRequired = (res: Response) => res.status === 426;

Try / catch

const res = await fetch(hmrUrl); if (res.status === 426) { /* wrong port in static mode: use the main server port */ }

Prevention

When it happens

Trigger: Hitting the derived HMR port (resolved from the main server port) while the server runs in static mode; a health/probe or browser tab pointed at the HMR port instead of the app port.

Common situations: Configuring a proxy or load balancer to the HMR companion port; opening the wrong port in a browser during local static-mode runs; monitoring checks scanning all listening ports.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/6ff41f25ccb5b8fc. Report an issue: GitHub.

Appendix: source

Thrown at server/src/app.ts:1013

          return;
        }
        res
          .status(200)
          .set("Content-Type", "text/html")
          .set("Cache-Control", "no-cache")
          .end(readBrandedStaticIndexHtml(uiDist));
      });
    } else {
      console.warn("[paperclip] UI dist not found; running in API-only mode");
    }
    if (process.env.PAPERCLIP_MANAGED_RUNTIME_EXPOSURE === "tailscale_https") {
      // The managed-runtime supervisor waits for the app port AND its derived
      // Vite HMR companion port to bind before publishing the service. Static
      // mode has no Vite, so bind the same placeholder listener dev mode uses
      // or the supervisor kills a healthy server at the readiness deadline
      // (PAP-18043).
      const hmrServer = createHttpServer((_req, res) => {
        res.writeHead(426, { "Content-Type": "text/plain" });
        res.end("Upgrade Required");
      });
      await listenViteHmrServer(
        hmrServer,
        resolveViteHmrPort(opts.serverPort),
        opts.bindHost,
      );
      viteHmrServer = hmrServer;
    }
  }

  if (opts.uiMode === "vite-dev") {
    const uiRoot = path.resolve(__dirname, "../../ui");
    const publicUiRoot = path.resolve(uiRoot, "public");
    const hmrPort = resolveViteHmrPort(opts.serverPort);
    const hmrHost = resolveViteHmrHost(opts.bindHost);
    const hmrProtocol = resolveViteHmrProtocol(
      process.env.PAPERCLIP_VITE_HMR_PROTOCOL,

View on GitHub (pinned to 3f1d897a7c)