{"record":{"id":"da556a399bce9d57","repo":"jackwener/OpenCLI","slug":"command-key-must-declare-access-read-writ","errorCode":null,"errorMessage":"Command ${key} must declare access: 'read' | 'write'","messagePattern":"Command (.+?) must declare access: 'read' \\| 'write'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/registry.ts","lineNumber":211,"sourceCode":"    if (strategy === Strategy.COOKIE && cmd.domain) {\n      navigateBefore = `https://${cmd.domain}`;\n    } else if (strategy !== Strategy.PUBLIC && strategy !== Strategy.LOCAL) {\n      // Non-PUBLIC without domain: needs authenticated browser context\n      // but no specific pre-navigation URL. `true` signals this to\n      // shouldUseBrowserSession without triggering resolvePreNav.\n      navigateBefore = true;\n    }\n  }\n\n  return browser\n    ? { ...cmd, strategy, browser: true, navigateBefore } as BrowserCliCommand\n    : { ...cmd, strategy, browser: false, navigateBefore } as NonBrowserCliCommand;\n}\n\nfunction assertCommandAccess(cmd: Pick<RawCliCommand, 'site' | 'name'> & { access?: unknown }): asserts cmd is RawCliCommand {\n  if (cmd.access === 'read' || cmd.access === 'write') return;\n  const key = `${cmd.site}/${cmd.name}`;\n  throw new Error(`Command ${key} must declare access: 'read' | 'write'`);\n}\n\nfunction assertSiteSession(cmd: Pick<RawCliCommand, 'site' | 'name'> & { siteSession?: unknown }): void {\n  if (cmd.siteSession === undefined) return;\n  const key = `${cmd.site}/${cmd.name}`;\n  if (cmd.siteSession !== 'ephemeral' && cmd.siteSession !== 'persistent') {\n    throw new Error(`Command ${key} siteSession must be one of: ephemeral, persistent`);\n  }\n}\n\nexport function registerCommand(cmd: RawCliCommand): void {\n  const normalized = normalizeCommand(cmd);\n  const canonicalKey = fullName(normalized);\n  const existing = _registry.get(canonicalKey);\n  if (existing?.aliases) {\n    for (const alias of existing.aliases) {\n      _registry.delete(`${existing.site}/${alias}`);\n    }","sourceCodeStart":193,"sourceCodeEnd":229,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/src/registry.ts#L193-L229","documentation":"assertCommandAccess validates every RawCliCommand registered via registerCommand/normalizeCommand: each command must explicitly declare access as 'read' or 'write'. The check exists so the CLI can enforce permission semantics (read-only vs mutating operations) per command; an undeclared access is treated as a registration-time programming error.","triggerScenarios":"Calling registerCommand(cmd) (directly or through a registry/bulk-registration path) with a command object whose access property is missing, undefined, or set to any value other than 'read' or 'write' (e.g. 'rw', true, null).","commonSituations":"Adding a new command definition and forgetting the access field; copying a command object and dropping the property; data-driven command tables where one row lacks access; typo like 'Read' (case-sensitive).","solutions":["Add access: 'read' or access: 'write' to the command definition before registering it.","Choose 'write' for any command that mutates site state, 'read' otherwise.","If commands come from a config/table, add a pre-registration check that every entry has a valid access value.","Fix casing/value typos — only the exact strings 'read' and 'write' pass."],"exampleFix":"// before\nregisterCommand({ site: \"github\", name: \"star-repo\", run: fn });\n// after\nregisterCommand({ site: \"github\", name: \"star-repo\", access: \"write\", run: fn });","handlingStrategy":"type-guard","validationCode":"type Access = \"read\" | \"write\";\nfunction hasValidAccess(cmd: { access?: unknown }): cmd is { access: Access } & Record<string, unknown> {\n  return cmd.access === \"read\" || cmd.access === \"write\";\n}\nif (!hasValidAccess(cmd)) throw new Error(`Command ${cmd.site}/${cmd.name} needs access`);\nregisterCommand(cmd);","typeGuard":"function declaresAccess(cmd: { access?: unknown }): cmd is { access: \"read\" | \"write\" } {\n  return cmd.access === \"read\" || cmd.access === \"write\";\n}","tryCatchPattern":"try {\n  registerCommand(cmd);\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"must declare access\")) {\n    console.error(`Fix command definition: ${e.message}`);\n  } else throw e;\n}","preventionTips":["Type command definitions as RawCliCommand so TypeScript requires the access field.","Add access to every new command template/boilerplate.","Write a unit test that registers all command definitions to fail fast.","Avoid hand-built command objects; use a factory that defaults access explicitly."],"tags":["registry","validation","command","permissions"],"backgroundTag":"missing-required-field","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}