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
- Switch the issue's work mode to one included in the tool's allowedModes (e.g., standard) before invoking the tool.
- Ask the agent to skip the tool call in the current mode, or restrict the tool from the model's toolset for that mode.
- Update the tool descriptor's allowedModes if the tool should legitimately run in the new mode.
- 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
- Filter the model's toolset to descriptors whose allowedModes include the current issue.workMode.
- Surface mode changes (ask/planning/standard) to the agent loop promptly.
- When adding modes or tools, update descriptor allowedModes in the same change.
- Pre-check issue.workMode against the descriptor before invoking mutating tools.
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
- access.reasonCode
- ACPX runtime executable must be a bounded executable file
- Could not prepare managed GitHub launchers
- currentAccess.reasonCode
- Discord bot needs View Channels, Send Messages, Create…
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)