garrytan/gstack · error · Error

tab-each rejected: subcommand "${innerRaw}" not allowed by y

Error message

tab-each rejected: subcommand "${innerRaw}" not allowed by your token scope (${tokenInfo.scopes.join(', ')}).

What it means

Thrown by tab-each when the caller's token does not have the required scope for the inner command. Before fanning out, tab-each canonicalizes the inner command name and runs checkScope(tokenInfo, innerName). Non-root tokens whose scopes do not include the inner command are rejected to prevent a single permission failure from partially mutating tabs — the scope check is done upfront so the whole batch aborts atomically.

Source

Thrown at browse/src/meta-commands.ts:328

      //   $B tab-each snapshot -i      → snapshot every tab
      //   $B tab-each text             → grab clean text from every tab
      //   $B tab-each goto https://x.y → load the same URL in every tab
      if (args.length === 0) {
        throw new Error(
          'Usage: browse tab-each <command> [args...]\n' +
          'Example: browse tab-each snapshot -i'
        );
      }

      const innerRaw = args[0];
      const innerName = canonicalizeCommand(innerRaw);
      const innerArgs = args.slice(1);

      // Scope check the inner command before fanning out, so a single
      // permission failure aborts the whole batch instead of partially
      // mutating tabs.
      if (tokenInfo && tokenInfo.clientId !== 'root' && !checkScope(tokenInfo, innerName)) {
        throw new Error(
          `tab-each rejected: subcommand "${innerRaw}" not allowed by your token scope (${tokenInfo.scopes.join(', ')}).`
        );
      }

      const tabs = await bm.getTabListWithTitles();
      const originalActive = tabs.find(t => t.active)?.id ?? bm.getActiveTabId();

      const executeCmd = opts?.executeCommand;
      const results: Array<{
        tabId: number;
        url: string;
        title: string;
        status: number;
        output: string;
      }> = [];

      try {
        for (const tab of tabs) {

View on GitHub (pinned to 94993f7401)

Solutions

  1. Use a token whose scopes include the inner command — check tokenInfo.scopes for the missing permission
  2. Switch to a root token (clientId === 'root') for operations requiring elevated scope
  3. Request the additional scope be added to the token by an administrator

Example fix

# before — read-only token trying a write command
$B tab-each goto https://example.com  # token scope: snapshot,text

# after — use a token with goto scope
# or use root token
Defensive patterns

Strategy: validation

Validate before calling

// Verify scope before fanning out
const innerName = canonicalizeCommand(args[0]);
if (tokenInfo && tokenInfo.clientId !== 'root' && !checkScope(tokenInfo, innerName)) {
  throw new Error(`Token lacks scope for '${innerName}'. Available: ${tokenInfo.scopes.join(', ')}`);
}

Type guard

function tokenHasScope(token: TokenInfo, cmd: string): boolean {
  return token.clientId === 'root' || checkScope(token, cmd);
}

Prevention

When it happens

Trigger: Calling 'browse tab-each <cmd>' with a scoped (non-root) token whose scopes array does not include the canonicalized inner command name.

Common situations: Using a limited-scope API token (e.g., read-only) and trying to fan out a write command like 'goto' or 'click'. Or the token was created with specific scopes that exclude the desired inner command.

Related errors


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