garrytan/gstack · error · Error

Usage: echo '[["goto","url"],["text"]]' | browse chain or

Error message

Usage: echo '[["goto","url"],["text"]]' | browse chain
   or: browse chain 'goto url | click @e5 | snapshot -ic'

What it means

The `chain` meta-command expects a single argument that is either a JSON array of command arrays (`[["goto","url"],["text"]]`) or a pipe-delimited script (`goto url | click @e5 | snapshot -ic`). When `args[0]` is empty/undefined the command cannot infer what to run, so it throws this usage message (lines 600-604).

Source

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

        validateOutputPath(screenshotPath);
        await page.screenshot({ path: screenshotPath, fullPage: true });
        await guardScreenshotPath(screenshotPath);
        results.push(`${vp.name} (${vp.width}x${vp.height}): ${screenshotPath}`);
      }

      // Restore original viewport
      if (originalViewport) {
        await page.setViewportSize(originalViewport);
      }

      return results.join('\n');
    }

    // ─── Chain ─────────────────────────────────────────
    case 'chain': {
      // Read JSON array from args[0] (if provided) or expect it was passed as body
      const jsonStr = args[0];
      if (!jsonStr) throw new Error(
        'Usage: echo \'[["goto","url"],["text"]]\' | browse chain\n' +
        '   or: browse chain \'goto url | click @e5 | snapshot -ic\''
      );

      let rawCommands: string[][];
      try {
        rawCommands = JSON.parse(jsonStr);
        if (!Array.isArray(rawCommands)) throw new Error('not array');
      } catch (err: any) {
        // Fallback: pipe-delimited format "goto url | click @e5 | snapshot -ic"
        if (!(err instanceof SyntaxError) && err?.message !== 'not array') throw err;
        rawCommands = jsonStr.split(' | ')
          .filter(seg => seg.trim().length > 0)
          .map(seg => tokenizePipeSegment(seg.trim()));
      }

      // Canonicalize aliases across the whole chain. Pair canonical name with the raw
      // input so result labels + error messages reflect what the user typed, but every

View on GitHub (pinned to 94993f7401)

Solutions

  1. Pass a JSON array as the first arg: `browse chain '[["goto","https://x"],["text"]]'`.
  2. Or pass a pipe-delimited script: `browse chain 'goto https://x | text'`.
  3. If piping via stdin, make sure the server/CLI forwards stdin into `args[0]` — otherwise inline the payload.

Example fix

// before
browse chain
// after
browse chain '[["goto","https://example.com"],["text"]]'
Defensive patterns

Strategy: validation

Validate before calling

if (!args[0] || typeof args[0] !== 'string') {
  throw new Error('chain requires a JSON array or pipe-delimited script as args[0]');
}

Type guard

const isChainPayload = (s: string): boolean => {
  try { return Array.isArray(JSON.parse(s)); }
  catch { return s.includes('|') || /\s/.test(s); }
};

Try / catch

try { await browse.chain(payload); }
catch (err) {
  if (/Usage:/.test(err.message) && /chain/.test(err.message)) {
    // rebuild payload as JSON array of [cmd, ...args]
  }
}

Prevention

When it happens

Trigger: Calling `browse chain` with no arguments, piping nothing on stdin when the server expected `args[0]`, or passing an empty string.

Common situations: First-time users forgetting the JSON shape; shell quoting that swallowed the argument; an agent constructing the call without a payload.

Related errors


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