paperclipai/paperclip · error · Error

paperclip_runner_tool_mode_denied

paperclip_runner_tool_mode_denied

Error message

paperclip_runner_tool_mode_denied

What it means

Runner tools are gated by the issue's work mode. Each tool descriptor declares allowedModes (e.g., standard/planning/ask); before executing, the authority checks the current issue.workMode against the descriptor. This error means the requested tool is not permitted in the issue's current work mode (e.g., a mutating tool during an 'ask' session).

Solutions

  1. Switch the issue's work mode to one included in the tool's allowedModes (e.g., standard) before invoking the tool.
  2. Ask the agent to skip the tool call in the current mode, or restrict the tool from the model's toolset for that mode.
  3. Update the tool descriptor's allowedModes if the tool should legitimately run in the new mode.
  4. Verify issue.workMode value is a recognized mode and the descriptor lookup uses the right tool name.

Example fix

// before
await patchIssue(issueId, { workMode: "ask" });
await runner.execute({ tool: "create_skill", arguments }); // denied in ask mode
// after
await patchIssue(issueId, { workMode: "standard" });
await runner.execute({ tool: "create_skill", arguments });
Defensive patterns

Strategy: validation

Validate before calling

function toolAllowedInMode(descriptor, workMode) {
  return !!descriptor && descriptor.allowedModes.includes(workMode);
}
// before execute: if (!toolAllowedInMode(descriptors[call.tool], issue.workMode)) skipTool(call);

Type guard

const isToolAllowed = (descriptor, workMode) => !!descriptor && descriptor.allowedModes.includes(workMode);

Try / catch

try {
  return await authority.execute(call);
} catch (e) {
  if (e.message === "paperclip_runner_tool_mode_denied") {
    return respondSkipped(`Tool ${call.tool} is not available in work mode ${context.issue.workMode}`);
  }
  throw e;
}

Prevention

When it happens

Trigger: execute() looks up the tool descriptor and either finds none, or descriptor.allowedModes does not include context.issue.workMode cast as "standard" | "planning" | "ask" — e.g., calling create_skill or create_project while the issue is in 'ask' or 'planning' mode.

Common situations: User switched the issue to 'ask' mode but the agent still attempts mutating tools; running in 'planning' mode where only read-only tools are allowed; a tool registered without the current mode in its allowedModes list after a mode introduction; stale UI mode while the agent loop keeps running old tool calls.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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

Appendix: source

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

        cursor:
          input.cursor === null || input.cursor === undefined
            ? null
            : requiredString(input.cursor),
      });
    }
    if (call.tool === REUSE_CHAT_ATTACHMENT_TOOL_NAME) {
      return this.#reuseChatAttachment(input);
    }
    const descriptor = CAPABILITY_SEMANTIC_TOOL_CATALOG.find(
      (candidate) => candidate.operationId === call.tool,
    );
    if (
      !descriptor ||
      !descriptor.allowedModes.includes(
        context.issue.workMode as "standard" | "planning" | "ask",
      )
    ) {
      throw new Error("paperclip_runner_tool_mode_denied");
    }
    switch (call.tool) {
      case "create_skill": {
        const apiUrl = this.binding.apiUrl ?? process.env.PAPERCLIP_API_URL;
        const token = createLocalAgentJwt(this.binding.agentId, this.binding.companyId, context.actor.adapterType, this.binding.runId, context.run.responsibleUserId);
        if (!apiUrl || !token) throw new Error("Skill tool authentication is unavailable");
        return callCreateSkillTool({ arguments: input, apiUrl, token, companyId: this.binding.companyId });
      }
      case "create_project":
      case "list_project_repositories":
      case "list_projects": {
        const apiUrl = this.binding.apiUrl ?? process.env.PAPERCLIP_API_URL;
        const token = createLocalAgentJwt(this.binding.agentId, this.binding.companyId, context.actor.adapterType, this.binding.runId, context.run.responsibleUserId);
        if (!apiUrl || !token) throw new Error("Project tool authentication is unavailable");
        return callProjectTool({ name: call.tool, arguments: input, apiUrl, token,
          companyId: this.binding.companyId, issueId: this.binding.issueId, agentId: this.binding.agentId,
          conversation: Boolean(context.issue.conversationAgentId) });
      }

View on GitHub (pinned to 3f1d897a7c)