theonedev/onedev · error · ExplicitException

Permission denied calling tool:

Error message

Permission denied calling tool: 

What it means

ToolUtils.handleCallException converts any UnauthorizedException found in a tool-call exception chain into an ExplicitException 'Permission denied calling tool: <name>'. This is the AI tool layer's way of surfacing authorization failures from underlying services to the chat user.

Solutions

  1. Grant the authenticated user the permissions needed for the specific tool's operations in OneDev.
  2. Run the AI integration under a service account with the appropriate project roles.
  3. Narrow tool usage to projects the user can access.

Example fix

// before
// user 'bot' has no role on project 'core'
chatClient.call("listIssues", Map.of("project", "core"));
// after
// add 'bot' as a member (e.g. Read role) of project 'core', then
chatClient.call("listIssues", Map.of("project", "core"));
Defensive patterns

Strategy: try-catch

Validate before calling

// verify user's project access before invoking the tool
const perms = await getProjectPermissions(user, projectPath);
if (!perms.canRead) throw new Error("User lacks access to project " + projectPath);

Try / catch

try { return callTool(name, args); } catch (e) { if (/Permission denied calling tool/.test(e.message)) { await requestAccessOrNotifyUser(name); return null; } throw e; }

Prevention

When it happens

Trigger: Any AI tool invocation whose execution path throws UnauthorizedException — e.g. the agent's user lacks project access required by the tool (reading issues, triggering builds, accessing code).

Common situations: AI agent operating as a user without membership in the target project; revoked permissions mid-session; tools accessing private projects.

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 theonedev/onedev@d44925c47c (2026-09-06). Data as JSON: /api/errors/79567487191dcf8b. Report an issue: GitHub.

Appendix: source

Thrown at server-core/src/main/java/io/onedev/server/ai/ToolUtils.java:98

		}
		return convertToJson(Map.of("successful", true, "filesAndFolders", filesAndFolders));
	}

	public static JsonNode getToolArguments(ToolExecutionRequest toolExecutionRequest) {
		var objectMapper = getObjectMapper();
		if (toolExecutionRequest.arguments() == null)
			return objectMapper.createObjectNode();

		try {
			return objectMapper.readTree(toolExecutionRequest.arguments());
		} catch (JsonProcessingException e) {
			throw new RuntimeException(e);
		}
	}

	public static void handleCallException(String toolName, Throwable exception) {
		if (ExceptionUtils.find(exception, UnauthorizedException.class) != null) 
			throw new ExplicitException("Permission denied calling tool: " + toolName);
		var explicitException = ExceptionUtils.find(exception, ExplicitException.class);
		if (explicitException != null) 
			throw explicitException;
		logger.error("Error calling tool: " + toolName, exception);
		throw new ExplicitException("Error calling tool '" + toolName + "', check server log for details");
	}

	public static ChatTool wrapForChat(TaskTool taskTool) {
		return new ChatTool() {

			@Override
			public ToolSpecification getSpecification() {
				return taskTool.getSpecification();
			}

			@Override
			public CompletableFuture<ToolExecutionResult> execute(@Nullable IPartialPageRequestHandler handler, Subject subject, JsonNode arguments) {
				return CompletableFuture.completedFuture(taskTool.execute(subject, arguments));

View on GitHub (pinned to d44925c47c)