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
- Point the cloud control request at POST/GET /api/instance/task-drain exactly (a trailing slash is tolerated)
- Use an HTTP method that maps to an expected action (currently the drain endpoint's configured methods)
- If adding a new cloud-control endpoint, extend ACTION_BY_METHOD with the method→action mapping
- 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
- Hardcode /api/instance/task-drain in the cloud orchestrator config
- Keep method→action mappings in sync when adding new cloud-control endpoints
- Normalize trailing slashes on both client and server
- Smoke-test cloud assertions against a dev instance after endpoint changes
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
- cloud_runtime_identity_wrong_endpoint
- invalid_cloud_control_assertion
- Announcement request failed
- Anthropic Managed Agents request failed with HTTP
- Artifact download failed: HTTP
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)