nanocoai/nanoclaw · error · Error
role not found
Error message
role not found
What it means
Thrown by `ncl roles revoke` when the DELETE on `user_roles` affected zero rows — no row matched the exact (user_id, role, agent_group_id) triple, including NULL-vs-NULL matching via `IS NOT DISTINCT FROM`. The role literally does not exist as granted, so there is nothing to revoke.
Source
Thrown at src/cli/resources/roles.ts:69
return { user_id: userId, role, agent_group_id: groupId };
},
},
revoke: {
access: 'approval',
description: 'Revoke a role. Use --user, --role, and --group if scoped.',
handler: async (args) => {
const userId = args.user as string;
const role = args.role as string;
const groupId = (args.group as string) ?? null;
if (!userId) throw new Error('--user is required');
if (!role) throw new Error('--role is required');
const result = await getDb().run(
'DELETE FROM user_roles WHERE user_id = ? AND role = ? AND agent_group_id IS NOT DISTINCT FROM ?',
userId,
role,
groupId,
);
if (result.changes === 0) throw new Error('role not found');
return { revoked: { user_id: userId, role, agent_group_id: groupId } };
},
},
},
});
View on GitHub (pinned to 294ef2aee8)
Solutions
- List actual roles: `ncl roles list` — confirm the exact user_id, role, and agent_group_id of the grant
- If the grant is group-scoped, add --group <agent-group-id>; if it is global, omit --group
- If the user id changed (e.g. platform handle rename), revoke using the old stored id or update the user record first
Example fix
# before (grant was scoped) ncl roles grant --user telegram:alice --role admin --group grp_123 ncl roles revoke --user telegram:alice --role admin # after ncl roles revoke --user telegram:alice --role admin --group grp_123
Defensive patterns
Strategy: try-catch
Validate before calling
const roles = await execNclJson(['roles', 'list']);
const exists = roles.some(r => r.user_id === userId && r.role === role &&
(r.agent_group_id ?? null) === (groupId ?? null));
if (!exists) { console.error('no such grant; nothing to revoke'); return; }
await execNcl(['roles', 'revoke', '--user', userId, '--role', role,
...(groupId ? ['--group', groupId] : [])]); Type guard
const isExactGrant = (row: RoleRow, userId: string, role: string, groupId?: string | null) => row.user_id === userId && row.role === role && ((row.agent_group_id ?? null) === (groupId ?? null));
Try / catch
try { await revoke(); } catch (e) { if (e.message === 'role not found') { /* idempotent: treat as success or log */ } else throw e; } Prevention
- Mirror the exact --group shape used at grant time when revoking
- Make revokes idempotent by treating 'role not found' as success in automation
- Snapshot `ncl roles list --json` before bulk role changes
When it happens
Trigger: `ncl roles revoke --user X --role admin` when X has owner but not admin; revoking without --group when the grant was group-scoped (agent_group_id mismatch); revoking with --group when the grant was global; misspelled user id or role.
Common situations: Scope mismatch between grant and revoke (global vs group-scoped admin) is the classic case — `IS NOT DISTINCT FROM` requires the NULL/non-NULL shape to line up exactly; running revoke twice; user id format drift (handle renamed on the platform so the stored user_id differs).
Related errors
- --user is required
- owner role is always global (do not pass --group)
- ${def.name} not found: ${id}
- group not found: ${id}
- No container config for group: ${id}
AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28).
Data as JSON: /api/errors/fd04c4e5599aea97.
Report an issue: GitHub.