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

  1. List actual roles: `ncl roles list` — confirm the exact user_id, role, and agent_group_id of the grant
  2. If the grant is group-scoped, add --group <agent-group-id>; if it is global, omit --group
  3. 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

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


AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28). Data as JSON: /api/errors/fd04c4e5599aea97. Report an issue: GitHub.