paperclipai/paperclip · error

cloud_control_wrong_endpoint

cloud_control_wrong_endpoint

Error message

cloud_control_wrong_endpoint

What it means

cloudControlMiddleware authenticates Paperclip Cloud control-plane assertions (signed JWS). Before verifying the signature it confirms the request actually targets /api/instance/task-drain with the method-appropriate action; a trailing slash is normalized so it matches Express non-strict routing. Any other path or missing action is rejected with 400 and code cloud_control_wrong_endpoint without attempting JWS verification.

Solutions

  1. Point the cloud control request at POST/GET /api/instance/task-drain exactly (a trailing slash is tolerated)
  2. Use an HTTP method that maps to an expected action (currently the drain endpoint's configured methods)
  3. If adding a new cloud-control endpoint, extend ACTION_BY_METHOD with the method→action mapping
  4. Check the cloud orchestrator's endpoint configuration for typos or stale base paths

Example fix

// before
await fetch(base + "/api/instance/tasks/drain", { method: "POST", headers: { assertion } });
// after
await fetch(base + "/api/instance/task-drain", { method: "POST", headers: { assertion } });
Defensive patterns

Strategy: validation

Validate before calling

const path = new URL(url).pathname.replace(/\/$/, "");
const allowed = { "/api/instance/task-drain": new Set(["POST", "GET"]) };
if (!(path in allowed) || !allowed[path].has(method)) {
  throw new Error(`cloud control endpoint must be /api/instance/task-drain, got ${method} ${path}`);
}

Try / catch

const res = await sendCloudControl(request);
if (res.status === 400 && res.body?.error === "cloud_control_wrong_endpoint") {
  fixEndpointConfiguration(); // align path/method with ACTION_BY_METHOD
}

Prevention

When it happens

Trigger: Paperclip Cloud (or a test client) sends an assertion-bearing request to any path other than /api/instance/task-drain (after trailing-slash normalization), or uses an HTTP method that has no entry in ACTION_BY_METHOD (e.g. DELETE/PUT).

Common situations: Cloud-side routing misconfiguration pointing assertions at the wrong instance endpoint; adding a new cloud-control endpoint but not registering its method in ACTION_BY_METHOD; an old cloud build calling a retired endpoint path.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


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

Appendix: source

Thrown at server/src/middleware/cloud-control.ts:40

 * is bound to exactly one method's action, and the header is rejected loudly
 * anywhere else so it can never become an ambient credential. Instances
 * without a Cloud stack identity reject every assertion. The browser-facing
 * Cloud proxy strips this header, and possession of the shared tenant-session
 * token cannot mint it.
 */
export function cloudControlMiddleware(): RequestHandler {
  return (req, res, next) => {
    const assertion = req.get(CLOUD_CONTROL_HEADER)?.trim();
    if (!assertion) {
      next();
      return;
    }
    const expectedAction = ACTION_BY_METHOD[req.method];
    // Express's non-strict routing treats a trailing slash as the same
    // route; the endpoint check must agree with it.
    const normalizedPath = req.path.length > 1 && req.path.endsWith("/") ? req.path.slice(0, -1) : req.path;
    if (normalizedPath !== "/api/instance/task-drain" || !expectedAction) {
      res.status(400).json({ error: "cloud_control_wrong_endpoint" });
      return;
    }
    try {
      verifyCloudControlAssertion({ compactJws: assertion, expectedAction });
    } catch (error) {
      logger.warn({ err: error }, "Rejected Cloud control assertion");
      res.status(401).json({ error: "invalid_cloud_control_assertion" });
      return;
    }
    req.actor = {
      type: "board",
      userId: "paperclip-cloud",
      userName: "Paperclip Cloud",
      userEmail: null,
      isInstanceAdmin: true,
      source: "cloud_control",
    };
    next();

View on GitHub (pinned to 3f1d897a7c)