jackwener/OpenCLI · error
Command ${key} must declare access: 'read' | 'write'
Error message
Command ${key} must declare access: 'read' | 'write' What it means
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.
Source
Thrown at src/registry.ts:211
if (strategy === Strategy.COOKIE && cmd.domain) {
navigateBefore = `https://${cmd.domain}`;
} else if (strategy !== Strategy.PUBLIC && strategy !== Strategy.LOCAL) {
// Non-PUBLIC without domain: needs authenticated browser context
// but no specific pre-navigation URL. `true` signals this to
// shouldUseBrowserSession without triggering resolvePreNav.
navigateBefore = true;
}
}
return browser
? { ...cmd, strategy, browser: true, navigateBefore } as BrowserCliCommand
: { ...cmd, strategy, browser: false, navigateBefore } as NonBrowserCliCommand;
}
function assertCommandAccess(cmd: Pick<RawCliCommand, 'site' | 'name'> & { access?: unknown }): asserts cmd is RawCliCommand {
if (cmd.access === 'read' || cmd.access === 'write') return;
const key = `${cmd.site}/${cmd.name}`;
throw new Error(`Command ${key} must declare access: 'read' | 'write'`);
}
function assertSiteSession(cmd: Pick<RawCliCommand, 'site' | 'name'> & { siteSession?: unknown }): void {
if (cmd.siteSession === undefined) return;
const key = `${cmd.site}/${cmd.name}`;
if (cmd.siteSession !== 'ephemeral' && cmd.siteSession !== 'persistent') {
throw new Error(`Command ${key} siteSession must be one of: ephemeral, persistent`);
}
}
export function registerCommand(cmd: RawCliCommand): void {
const normalized = normalizeCommand(cmd);
const canonicalKey = fullName(normalized);
const existing = _registry.get(canonicalKey);
if (existing?.aliases) {
for (const alias of existing.aliases) {
_registry.delete(`${existing.site}/${alias}`);
}View on GitHub (pinned to 49907e53dc)
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.
Example fix
// before
registerCommand({ site: "github", name: "star-repo", run: fn });
// after
registerCommand({ site: "github", name: "star-repo", access: "write", run: fn }); Defensive patterns
Strategy: type-guard
Validate before calling
type Access = "read" | "write";
function hasValidAccess(cmd: { access?: unknown }): cmd is { access: Access } & Record<string, unknown> {
return cmd.access === "read" || cmd.access === "write";
}
if (!hasValidAccess(cmd)) throw new Error(`Command ${cmd.site}/${cmd.name} needs access`);
registerCommand(cmd); Type guard
function declaresAccess(cmd: { access?: unknown }): cmd is { access: "read" | "write" } {
return cmd.access === "read" || cmd.access === "write";
} Try / catch
try {
registerCommand(cmd);
} catch (e) {
if (e instanceof Error && e.message.includes("must declare access")) {
console.error(`Fix command definition: ${e.message}`);
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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).
Understand the failure class
Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.
Related errors
- Command ${key} siteSession must be one of: ephemeral, persis
- INVALID_ARGUMENT
- INVALID_ARGUMENT
- ${label} is required
- ${label} returned an unexpected payload shape; expected an o
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/da556a399bce9d57.
Report an issue: GitHub.