{"record":{"id":"0db121a13c4068f5","repo":"garrytan/gstack","slug":"invalid-scope-s-valid-validscopes-join","errorCode":null,"errorMessage":"Invalid scope: ${s}. Valid: ${validScopes.join(', ')}","messagePattern":"Invalid scope: (.+?)\\. Valid: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"browse/src/token-registry.ts","lineNumber":206,"sourceCode":"/**\n * Create a scoped session token (for direct minting via CLI or /token endpoint).\n * Only callable by root token holder.\n */\nexport function createToken(opts: CreateTokenOptions): TokenInfo {\n  const {\n    clientId,\n    scopes = ['read', 'write'],\n    domains,\n    tabPolicy = 'own-only',\n    rateLimit = 10,\n    expiresSeconds = 86400, // 24h default\n  } = opts;\n\n  // Validate inputs\n  const validScopes: ScopeCategory[] = ['read', 'write', 'admin', 'meta', 'control'];\n  for (const s of scopes) {\n    if (!validScopes.includes(s as ScopeCategory)) {\n      throw new Error(`Invalid scope: ${s}. Valid: ${validScopes.join(', ')}`);\n    }\n  }\n  if (rateLimit < 0) throw new Error('rateLimit must be >= 0');\n  if (expiresSeconds !== null && expiresSeconds !== undefined && expiresSeconds < 0) {\n    throw new Error('expiresSeconds must be >= 0 or null');\n  }\n\n  const token = generateToken('gsk_sess_');\n  const now = new Date();\n  const expiresAt = expiresSeconds === null\n    ? null\n    : new Date(now.getTime() + expiresSeconds * 1000).toISOString();\n\n  const info: TokenInfo = {\n    token,\n    clientId,\n    type: 'session',\n    scopes,","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/browse/src/token-registry.ts#L188-L224","documentation":"Thrown by createToken when one of the scopes passed in opts.scopes is not in the closed set {read, write, admin, meta, control}. Scope names are case-sensitive and there is no aliasing, so a typo or a deprecated name will fail at minting time before the token is ever issued.","triggerScenarios":"Calling createToken with scopes including a typo (`['rad']`), a deprecated name (`'execute'`), wrong case (`'Read'`), or a value intended for a different field.","commonSituations":"Upgrading from a version that previously accepted a different scope vocabulary; copy-pasting scope names from internal docs that drifted from the code; passing the clientId or domain list into the scopes field by mistake.","solutions":["Use only: read, write, admin, meta, control (lowercase).","If you need a privilege not in the set, request an admin scope and gate the action server-side.","Cross-check against the ScopeCategory type exported from token-registry.ts.","Add a unit test that asserts your scope list is a subset of the valid set."],"exampleFix":"// before\ncreateToken({ clientId: 'bot', scopes: ['Read', 'execute'] });\n// after\ncreateToken({ clientId: 'bot', scopes: ['read', 'admin'] });","handlingStrategy":"validation","validationCode":"const VALID_SCOPES = ['read','write','admin','meta','control'] as const;\ntype Scope = typeof VALID_SCOPES[number];\nfunction normalizeScopes(input: string[]): Scope[] {\n  for (const s of input) {\n    if (!VALID_SCOPES.includes(s as Scope)) {\n      throw new Error(`Invalid scope: ${s}. Valid: ${VALID_SCOPES.join(', ')}`);\n    }\n  }\n  return input as Scope[];\n}","typeGuard":"const isScope = (s: string): s is typeof VALID_SCOPES[number] =>\n  ['read','write','admin','meta','control'].includes(s as any);","tryCatchPattern":"try {\n  return createToken({ clientId, scopes });\n} catch (e: any) {\n  if (/^Invalid scope:/.test(e.message)) {\n    // drop the unknown scope and retry with the safe subset\n    const safe = scopes.filter(isScope);\n    return createToken({ clientId, scopes: safe });\n  }\n  throw e;\n}","preventionTips":["Type scopes against the ScopeCategory exported type so the compiler rejects typos.","Lower-case and trim scope input from user-facing configs.","Keep a unit test asserting your scope list ⊆ valid set.","When upgrading browse, diff the VALID_SCOPES list for renames."],"tags":["input-validation","tokens","authorization","scopes","rbac"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}