{"record":{"id":"11411809ef8b7c0c","repo":"nanocoai/nanoclaw","slug":"owner-role-is-always-global-do-not-pass-group","errorCode":null,"errorMessage":"owner role is always global (do not pass --group)","messagePattern":"owner role is always global \\(do not pass --group\\)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/cli/resources/roles.ts","lineNumber":40,"sourceCode":"      description:\n        'Null = global (all groups). A specific ID limits the role to that group. Owner must always be null.',\n    },\n    { name: 'granted_by', type: 'string', description: 'Who granted this role. Informational.' },\n    { name: 'granted_at', type: 'string', description: 'Auto-set.' },\n  ],\n  operations: { list: 'open' },\n  customOperations: {\n    grant: {\n      access: 'approval',\n      description: 'Grant a role. Use --user, --role, and optionally --group for scoped admin.',\n      handler: async (args) => {\n        const userId = args.user as string;\n        const role = args.role as string;\n        const groupId = (args.group as string) ?? null;\n        const grantedBy = (args.granted_by as string) ?? null;\n        if (!userId) throw new Error('--user is required');\n        if (!role || !['owner', 'admin'].includes(role)) throw new Error('--role must be owner or admin');\n        if (role === 'owner' && groupId) throw new Error('owner role is always global (do not pass --group)');\n        await getDb().run(\n          `INSERT INTO user_roles (user_id, role, agent_group_id, granted_by, granted_at)\n             VALUES (?, ?, ?, ?, ?)\n             ON CONFLICT DO NOTHING`,\n          userId,\n          role,\n          groupId,\n          grantedBy,\n          new Date().toISOString(),\n        );\n        return { user_id: userId, role, agent_group_id: groupId };\n      },\n    },\n    revoke: {\n      access: 'approval',\n      description: 'Revoke a role. Use --user, --role, and --group if scoped.',\n      handler: async (args) => {\n        const userId = args.user as string;","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/nanocoai/nanoclaw/blob/294ef2aee85218b23ad30eda9dfe10e590b54a8c/src/cli/resources/roles.ts#L22-L58","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nncl roles grant --user telegram:alice --role owner --group grp_123\n# after\nncl roles grant --user telegram:alice --role owner","handlingStrategy":"validation","validationCode":"// before invoking: role=owner must not carry a group\nconst role = 'owner';\nconst flags = ['--user', userId, '--role', role];\nif (group && role !== 'owner') flags.push('--group', group);\nawait ncl(['roles', 'grant', ...flags]);","typeGuard":"const isGrantableRoleCombo = (role: string, group?: string | null) =>\n  (role === 'owner' || role === 'admin') && !(role === 'owner' && group);","tryCatchPattern":"catch (e) { if (e.message.includes('owner role is always global')) { retry without --group } else throw e; }","preventionTips":["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"],"tags":["cli","roles","permissions","validation"],"backgroundTag":"invalid-cli-arguments","analyzedSha":"294ef2aee85218b23ad30eda9dfe10e590b54a8c","analyzedAt":"2026-08-28T13:59:10.357Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}