garrytan/gstack · error · Error
Unknown command: ${c.rawName}
Error message
Unknown command: ${c.rawName} What it means
In the CLI/fallback dispatch path (when `opts.executeCommand` is not provided), each chain subcommand is routed by checking membership in `WRITE_COMMANDS`, `READ_COMMANDS`, then `META_COMMANDS` (lines 680-697). If the canonical name matches none of these sets, the dispatcher throws `Unknown command`. This path runs only in direct-CLI mode; the server path delegates to `handleCommandInternal` instead.
Source
Thrown at browse/src/meta-commands.ts:697
let result: string;
if (WRITE_COMMANDS.has(name)) {
if (bm.isWatching()) {
result = 'BLOCKED: write commands disabled in watch mode';
} else {
result = await handleWriteCommand(name, cmdArgs, session, bm);
}
lastWasWrite = true;
} else if (READ_COMMANDS.has(name)) {
result = await handleReadCommand(name, cmdArgs, session);
if (PAGE_CONTENT_COMMANDS.has(name)) {
result = wrapUntrustedContent(result, bm.getCurrentUrl());
}
lastWasWrite = false;
} else if (META_COMMANDS.has(name)) {
result = await handleMetaCommand(name, cmdArgs, bm, shutdown, tokenInfo, opts);
lastWasWrite = false;
} else {
throw new Error(`Unknown command: ${c.rawName}`);
}
results.push(`[${label}] ${result}`);
} catch (err: any) {
results.push(`[${label}] ERROR: ${err.message}`);
}
}
}
// Wait for network to settle after write commands before returning
if (lastWasWrite) {
await bm.getPage().waitForLoadState('networkidle', { timeout: 2000 }).catch(() => {});
}
return results.join('\n\n');
}
// ─── Diff ──────────────────────────────────────────
case 'diff': {View on GitHub (pinned to 94993f7401)
Solutions
- Check the subcommand spelling and use the canonical name (run via the server for alias normalization).
- If the command is real but only registered server-side, invoke chain through the server (which sets `opts.executeCommand`) instead of the CLI fallback.
- Consult the command registry (`WRITE_COMMANDS`/`READ_COMMANDS`/`META_COMMANDS`) to confirm the canonical name.
Example fix
// before browse chain '[["got","https://x"]]' // typo // after browse chain '[["goto","https://x"]]'
Defensive patterns
Strategy: validation
Validate before calling
import { READ_COMMANDS, WRITE_COMMANDS, META_COMMANDS, canonicalizeCommand } from './commands';
const KNOWN = new Set([...READ_COMMANDS, ...WRITE_COMMANDS, ...META_COMMANDS]);
const unknown = chainCmds.filter(c => !KNOWN.has(canonicalizeCommand(c)));
if (unknown.length) throw new Error(`Unknown chain subcommand(s): ${unknown.join(', ')}`); Type guard
const isKnownCommand = (name: string): boolean => READ_COMMANDS.has(name) || WRITE_COMMANDS.has(name) || META_COMMANDS.has(name);
Try / catch
try { await browse.chain(payload); }
catch (err) {
if (/Unknown command/.test(err.message)) {
// surface the bad subcommand to the caller; do NOT silently retry
}
} Prevention
- Validate subcommand names against the registry before dispatch.
- Route chains through the server (which normalizes aliases) rather than the CLI fallback path.
- After renames, grep old command names across calling code.
When it happens
Trigger: A chain subcommand whose canonical name is not registered in any of the three command sets — e.g. a typo (`[["got","https://x"]]`), a removed/renamed command, or a command only reachable via server dispatch being run in CLI fallback mode.
Common situations: Running `browse chain` from the CLI (no server) with a command name that exists in the server registry but not in the local sets, or after a rename where old scripts still reference the previous name.
Related errors
- Usage: echo '[["goto","url"],["text"]]' | browse chain or
- pdf: --from-file requires a path
- Unknown pdf flag: ${a}
- pdf: --margins is mutex with --margin-top/--margin-right/--m
- pdf: --format is mutex with --width/--height
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/71124778a8209df7.
Report an issue: GitHub.