nanocoai/nanoclaw · error · Error
--role must be owner or admin
Error message
--role must be owner or admin
What it means
Thrown by `ncl roles grant` when --role is missing or is not one of the two supported values ('owner' or 'admin'). The user_roles table only models these two roles, so anything else is rejected before insert.
Source
Thrown at src/cli/resources/roles.ts:39
type: 'string',
description:
'Null = global (all groups). A specific ID limits the role to that group. Owner must always be null.',
},
{ name: 'granted_by', type: 'string', description: 'Who granted this role. Informational.' },
{ name: 'granted_at', type: 'string', description: 'Auto-set.' },
],
operations: { list: 'open' },
customOperations: {
grant: {
access: 'approval',
description: 'Grant a role. Use --user, --role, and optionally --group for scoped admin.',
handler: async (args) => {
const userId = args.user as string;
const role = args.role as string;
const groupId = (args.group as string) ?? null;
const grantedBy = (args.granted_by as string) ?? null;
if (!userId) throw new Error('--user is required');
if (!role || !['owner', 'admin'].includes(role)) throw new Error('--role must be owner or admin');
if (role === 'owner' && groupId) throw new Error('owner role is always global (do not pass --group)');
await getDb().run(
`INSERT INTO user_roles (user_id, role, agent_group_id, granted_by, granted_at)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT DO NOTHING`,
userId,
role,
groupId,
grantedBy,
new Date().toISOString(),
);
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) => {View on GitHub (pinned to 294ef2aee8)
Solutions
- Use exactly `--role owner` or `--role admin` (lowercase)
- For unprivileged access, use `ncl members add` instead of a role
- For scoped admin, add --group <gid>; for owner, omit --group
Example fix
// before ncl roles grant --user telegram:alice --role member // after ncl roles grant --user telegram:alice --role admin --group grp1
Defensive patterns
Strategy: type-guard
Validate before calling
const ROLES = ['owner', 'admin'] as const; if (!ROLES.includes(role)) throw new Error(`--role must be one of ${ROLES.join('|')}`); Type guard
const isValidRole = (r: unknown): r is 'owner' | 'admin' => r === 'owner' || r === 'admin';
Prevention
- Only two roles exist; use `ncl members add` for unprivileged access
- Lowercase and quote role values in scripts
When it happens
Trigger: Omitting --role entirely; passing 'member', 'moderator', ' Approver', or another string the whitelist does not include.
Common situations: Operators coming from systems with richer role models try to invent roles; quoting/case mistakes in scripts produce values like 'Admin' that fail the exact-match check.
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
- --from and --to must differ (self-messages are never gated)
- owner role is always global (do not pass --group)
- --role is required
- --${column.name.replace(/_/g, '-')} must be true or false
- ${col.name} must be one of: ${col.enum.join(', ')}
AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28).
Data as JSON: /api/errors/82eebd7eed280d4e.
Report an issue: GitHub.