{"record":{"id":"82eebd7eed280d4e","repo":"nanocoai/nanoclaw","slug":"role-must-be-owner-or-admin","errorCode":null,"errorMessage":"--role must be owner or admin","messagePattern":"--role must be owner or admin","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/cli/resources/roles.ts","lineNumber":39,"sourceCode":"      type: 'string',\n      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) => {","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/nanocoai/nanoclaw/blob/294ef2aee85218b23ad30eda9dfe10e590b54a8c/src/cli/resources/roles.ts#L21-L57","documentation":"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.","triggerScenarios":"Omitting --role entirely; passing 'member', 'moderator', ' Approver', or another string the whitelist does not include.","commonSituations":"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.","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"],"exampleFix":"// before\nncl roles grant --user telegram:alice --role member\n// after\nncl roles grant --user telegram:alice --role admin --group grp1","handlingStrategy":"type-guard","validationCode":"const ROLES = ['owner', 'admin'] as const; if (!ROLES.includes(role)) throw new Error(`--role must be one of ${ROLES.join('|')}`);","typeGuard":"const isValidRole = (r: unknown): r is 'owner' | 'admin' => r === 'owner' || r === 'admin';","tryCatchPattern":null,"preventionTips":["Only two roles exist; use `ncl members add` for unprivileged access","Lowercase and quote role values in scripts"],"tags":["cli","invalid-argument","roles","validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"294ef2aee85218b23ad30eda9dfe10e590b54a8c","analyzedAt":"2026-08-28T13:59:10.357Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}