nanocoai/nanoclaw · error · Error
owner role is always global (do not pass --group)
Error message
owner role is always global (do not pass --group)
What it means
Thrown by the `ncl roles grant` handler when the caller passes `--role owner` together with `--group`. NanoClaw's permission model treats the owner role as inherently global — it grants unrestricted access across all agent groups — so a group-scoped owner row is meaningless and the CLI rejects it before touching the `user_roles` table.
Source
Thrown at src/cli/resources/roles.ts:40
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) => {
const userId = args.user as string;View on GitHub (pinned to 294ef2aee8)
Solutions
- Re-run without the group flag: `ncl roles grant --user <id> --role owner`
- If a group-scoped privileged role is actually wanted, grant admin instead: `ncl roles grant --user <id> --role admin --group <agent-group-id>`
- In scripts, only include --group when role is admin
Example fix
# before ncl roles grant --user telegram:alice --role owner --group grp_123 # after ncl roles grant --user telegram:alice --role owner
Defensive patterns
Strategy: validation
Validate before calling
// before invoking: role=owner must not carry a group
const role = 'owner';
const flags = ['--user', userId, '--role', role];
if (group && role !== 'owner') flags.push('--group', group);
await ncl(['roles', 'grant', ...flags]); Type guard
const isGrantableRoleCombo = (role: string, group?: string | null) => (role === 'owner' || role === 'admin') && !(role === 'owner' && group);
Try / catch
catch (e) { if (e.message.includes('owner role is always global')) { retry without --group } else throw e; } Prevention
- Treat owner as global-only in any role-management script
- Build flag arrays conditionally instead of always appending --group
- Validate role+group combos in a wrapper before shelling out to ncl
When it happens
Trigger: Running `ncl roles grant --user <id> --role owner --group <agent-group-id>` (any non-empty --group value combined with role=owner). The check fires only in the grant verb, after the --user and --role validations pass.
Common situations: Operators copying an admin-grant command and changing the role to owner without dropping --group; scripting role grants generically so --group is always passed; migrating from a system where owners were scoped per workspace.
Related errors
- --user is required
- --role must be owner or admin
- --role is required
- role not found
- --${column.name.replace(/_/g, '-')} must be true or false
AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28).
Data as JSON: /api/errors/11411809ef8b7c0c.
Report an issue: GitHub.