paperclipai/paperclip · critical

Paperclip run authentication is unavailable

Error message

Paperclip run authentication is unavailable

What it means

PaperclipRunnerToolAuthority.#callApi mints a short-lived local agent JWT (createLocalAgentJwt) before dispatching any runner API call. createLocalAgentJwt returns null when jwtConfig() cannot produce a signing configuration (missing JWT secret/keys), so the authority throws this error instead of sending an unauthenticated request. It is a fail-closed guard: no run authentication material means the runner tool cannot call the Paperclip API at all.

Source

Thrown at server/src/services/native-runtime/paperclip-runner-tool-authority.ts:249

      }
      case "report_progress": return this.#reportProgress(input);
      case "request_human_input": return this.#requestHumanInput(input,
        (await captureRunIdentity(this.db, this.binding)).context?.id ?? null);
      case "create_task": return this.#createTask(input,
        (await captureRunIdentity(this.db, this.binding)).context?.id ?? null);
      case "set_dependencies": return this.#setDependencies(input);
      default: throw new Error("paperclip_runner_tool_not_bound");
    }
  }

  async #callApi(callId: string, value: unknown): Promise<unknown> {
    const bound = await this.#boundContext();
    const context = { ...this.binding, issueIdentifier: bound.issue.identifier, workMode: bound.issue.workMode };
    const { input, operation } = validateRunnerApiCall(value, context);
    const apiUrl = this.binding.apiUrl ?? process.env.PAPERCLIP_API_URL;
    if (!apiUrl) throw new Error("Paperclip API origin is unavailable");
    const token = createLocalAgentJwt(this.binding.agentId, this.binding.companyId, bound.actor.adapterType, this.binding.runId, bound.run.responsibleUserId);
    if (!token) throw new Error("Paperclip run authentication is unavailable");
    const execute = async () => {
      const current = await this.#boundContext();
      if (!runnerApiToolsEnabled(this.binding.companyId, this.binding.apiToolsEnabled)) throw new Error("paperclip_runner_tool_not_advertised");
      return executeRunnerApi(input, { ...context, workMode: current.issue.workMode }, {
        apiUrl, token,
        beforeDispatch: async () => {
          const fresh = await this.#boundContext();
          if (!runnerApiToolsEnabled(this.binding.companyId, this.binding.apiToolsEnabled)) throw new Error("paperclip_runner_tool_not_advertised");
          validateRunnerApiCall(input, { ...context, workMode: fresh.issue.workMode });
        },
        readFile: (file) => this.#readApiFile(file),
        saveResponse: async (bytes, contentType) => {
          const storage = this.binding.storage ?? getStorageService();
          const saved = await storage.putFile({ companyId: this.binding.companyId, namespace: "runner-api", originalFilename: contentType.includes("json") ? "response.json" : "response.bin", contentType, body: bytes });
          const asset = await assetService(this.db).create(this.binding.companyId, { ...saved, createdByAgentId: this.binding.agentId });
          const activity = await persistActivity(this.db, { companyId: this.binding.companyId, actorType: "agent", actorId: this.binding.agentId, agentId: this.binding.agentId, runId: this.binding.runId, issueId: this.binding.issueId, action: "asset.created", entityType: "asset", entityId: asset.id, details: { source: "runner.call_api", byteSize: saved.byteSize } });
          publishActivity(activity.publication);
          return { artifactId: asset.id, url: `/api/assets/${asset.id}/content`, contentType, byteSize: saved.byteSize, sha256: saved.sha256 };

View on GitHub (pinned to 01ad858492)

Solutions

  1. Configure the server's JWT signing configuration (secret/keys) so jwtConfig() returns a valid config, then restart the API server.
  2. Verify PAPERCLIP_API_URL (or binding.apiUrl) is also set, since the preceding check requires an API origin before token minting.
  3. Check server startup logs for JWT config initialization warnings and re-run the deployment/config setup steps.
  4. If running tests, initialize the JWT config in the test setup before exercising runner API tools.

Example fix

// before
// server started with no JWT secret; createLocalAgentJwt() -> null
// after
// .env
PAPERCLIP_JWT_SECRET=<generated-256-bit-secret>
// restart server, then the runner tool can mint the local agent JWT
Defensive patterns

Strategy: try-catch

Validate before calling

import { jwtConfig } from "../agent-auth-jwt.js";
const canMint = typeof jwtConfig === "function" && jwtConfig() != null; // run before enabling runner tools
if (!canMint || !process.env.PAPERCLIP_API_URL) throw new Error("Runner API prerequisites missing: JWT config and PAPERCLIP_API_URL required");

Type guard

function hasRunAuth(t: string | null | undefined): t is string { return typeof t === "string" && t.length > 0; }

Try / catch

try {
  const result = await authority.callApi(input);
} catch (err) {
  if (err instanceof Error && err.message === "Paperclip run authentication is unavailable") {
    // surface a config error: JWT signing config missing on the server
  } else throw err;
}

Prevention

When it happens

Trigger: An agent tool call to search_api/call_api during a native run where createLocalAgentJwt(agentId, companyId, adapterType, runId, responsibleUserId) returns null — i.e. the server's JWT signing config is absent (jwtConfig() falsy), typically because no JWT secret is configured on the instance.

Common situations: Self-hosted/dev instances started without JWT configuration in env or config file; config was reset or migrated and the signing key was dropped; running the runner authority in a test harness where jwtConfig() is not initialized.

Understand the failure class

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/8a561bf274c48ab6. Report an issue: GitHub.