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

  1. Check the subcommand spelling and use the canonical name (run via the server for alias normalization).
  2. If the command is real but only registered server-side, invoke chain through the server (which sets `opts.executeCommand`) instead of the CLI fallback.
  3. 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

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


AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12). Data as JSON: /api/errors/71124778a8209df7. Report an issue: GitHub.