paperclipai/paperclip · error · HttpError

cloud_portfolio_not_configured

cloud_portfolio_not_configured

Error message

Paperclip Cloud portfolio is not configured

What it means

Cloud-stack context failure in cloudRoutes: runtimeEnv carries no Paperclip Cloud portfolio configuration (no portfolio URL/tenant setup), so the /stacks route cannot build an upstream request. Signaled as an HTTP error rather than an empty list so the UI can show the feature is unavailable.

Solutions

  1. Configure the Paperclip Cloud portfolio settings for this instance before calling the cloud portfolio endpoint.
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at server/src/routes/cloud.ts:43 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of paperclipai/paperclip@120ae5428f (2026-08-18). Data as JSON: /api/errors/79049fcb0e2e6643. Report an issue: GitHub.

Appendix: source

Thrown at server/src/routes/cloud.ts:43

  const fetchImpl = opts.fetchImpl ?? fetch;
  const now = opts.now ?? Date.now;
  const cacheTtlMs = opts.cacheTtlMs ?? DEFAULT_PORTFOLIO_CACHE_TTL_MS;
  const portfolioCache = new Map<string, PortfolioCacheEntry>();

  router.get("/stacks", async (req, res) => {
    const context = getCloudStackContext(runtimeEnv);
    if (!context) {
      throw notFound("Cloud stack portfolio is unavailable");
    }
    if (req.actor.source !== "cloud_tenant" || !req.actor.userId?.trim()) {
      throw forbidden("Trusted Cloud tenant access required", {
        code: "cloud_tenant_required",
      });
    }

    const tenantToken = runtimeEnv.PAPERCLIP_CLOUD_TENANT_SERVER_TOKEN?.trim();
    if (!context.cloudOrigin || !context.stackId || !tenantToken) {
      throw new HttpError(503, "Paperclip Cloud portfolio is not configured", {
        code: "cloud_portfolio_not_configured",
      });
    }

    const actorUserId = req.actor.userId.trim();
    const cacheKey = `${context.cloudOrigin}\n${context.stackId}\n${actorUserId}`;
    const currentTime = now();
    for (const [key, entry] of portfolioCache) {
      if (entry.expiresAt <= currentTime) portfolioCache.delete(key);
    }
    const cached = portfolioCache.get(cacheKey);
    if (cached) {
      res.setHeader("Cache-Control", "no-store");
      res.json(cached.payload);
      return;
    }

    let portfolioUrl: URL;

View on GitHub (pinned to 120ae5428f)