theonedev/onedev · error · ExplicitException

Cannot set password for service or AI account

Error message

Cannot set password for service or AI account

What it means

Passwords can only be set on ordinary user accounts. If the target user's type is not ORDINARY — e.g. a service account or an AI account — the setPassword endpoint rejects the request. Service/AI accounts are not interactive-login accounts and must not hold local passwords.

Source

Thrown at server-core/src/main/java/io/onedev/server/rest/resource/UserResource.java:491

		if (userId <= User.ROOT_ID)		
			throw new BadRequestException("Should only convert normal users to service accounts");
		var user = userService.load(userId);
		userService.convertToServiceAccount(user);

		auditService.audit(null, "converted user \"" + user.getName() + "\" to service account via RESTful API", null, null);

		return Response.ok().build();
    }
	
	@Api(order=2000)
	@Path("/{userId}/password")
    @POST
    public Response setPassword(@PathParam("userId") Long userId, @Password(checkPolicy=true) @NotEmpty String password) {
    	User user = userService.load(userId);
		if (user.isDisabled()) {
			throw new ExplicitException("Cannot set password for disabled account");
		} else if (user.getType() != ORDINARY) {
			throw new ExplicitException("Cannot set password for service or AI account");
		} if (SecurityUtils.isAdministrator()) {
			user.setPassword(passwordService.encryptPassword(password));
			userService.update(user, null);
			if (!getAuthUser().equals(user)) 
				auditService.audit(null, "changed password of account \"" + user.getName() + "\" via RESTful API", null, null);
			return Response.ok().build();
		} else if (user.equals(getAuthUser())) {
			if (user.getPassword() == null) {
				throw new ExplicitException("The user is currently authenticated via external system, "
						+ "please change password there instead");
			} else {
				user.setPassword(passwordService.encryptPassword(password));
				userService.update(user, null);
				return Response.ok().build();
			}			
    	} else {
			throw new UnauthorizedException();
		}

View on GitHub (pinned to d44925c47c)

Solutions

  1. Verify the target account's type is ORDINARY before calling the password endpoint
  2. Remove service/AI accounts from bulk password-set operations
  3. If credential-like access is needed for a service account, use access tokens instead of passwords

Example fix

// before
await rest.post(`/users/${svc.id}/password`, {password}); // 400: not ORDINARY
// after
if (svc.type === 'ORDINARY') {
  await rest.post(`/users/${svc.id}/password`, {password});
} else {
  await rest.post(`/users/${svc.id}/tokens`, ...); // access token instead
}
Defensive patterns

Strategy: validation

Validate before calling

const user = await rest.get(`/users/${userId}`); if (user.type !== 'ORDINARY') throw new Error('Passwords can only be set on ordinary accounts');

Type guard

function isOrdinaryUser(user) { return user.type === 'ORDINARY'; }

Try / catch

try { await rest.post(`/users/${id}/password`, {password}); } catch (e) { if (e.status === 400 && /service or AI account/.test(e.message)) { /* use access tokens instead */ } else throw e; }

Prevention

When it happens

Trigger: Calling POST /rest/v1/users/{userId}/password where the user's type is SERVICE or AI instead of ORDINARY.

Common situations: Scripts that iterate all users and try to set passwords on service accounts; mistaking a service account id for a normal user's; automation tooling that syncs credentials across all account types.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06). Data as JSON: /api/errors/0da6dcedccc8b264. Report an issue: GitHub.