jackwener/OpenCLI · error · CommandExecutionError

Browser session required for twitter reply-dm

Error message

Browser session required for twitter reply-dm

What it means

CommandExecutionError thrown at the start of the twitter reply-dm command when the `page` argument is falsy — the batch DM-reply operation requires an active browser session and cannot enumerate conversations without one.

Source

Thrown at clis/twitter/reply-dm.js:20

import { cli, Strategy } from '@jackwener/opencli/registry';
cli({
    site: 'twitter',
    name: 'reply-dm',
    access: 'write',
    description: 'Send a message to recent DM conversations',
    domain: 'x.com',
    strategy: Strategy.UI,
    browser: true,
    args: [
        { name: 'text', type: 'string', required: true, positional: true, help: 'Message text to send (e.g. "我的微信 wxkabi")' },
        { name: 'max', type: 'int', required: false, default: 20, help: 'Maximum number of conversations to reply to (default: 20)' },
        { name: 'skip-replied', type: 'boolean', required: false, default: true, help: 'Skip conversations where you already sent the same text (default: true)' },
        { name: 'timeout', type: 'int', required: false, default: 600, help: 'Max seconds for the overall command (default: 600 — batch op)' },
    ],
    columns: ['index', 'status', 'user', 'message'],
    func: async (page, kwargs) => {
        if (!page)
            throw new CommandExecutionError('Browser session required for twitter reply-dm');
        const messageText = kwargs.text;
        const maxSend = kwargs.max ?? 20;
        const skipReplied = kwargs['skip-replied'] !== false;
        const results = [];
        let sentCount = 0;
        // Step 1: Navigate to messages to get conversation list
        await page.goto('https://x.com/messages');
        await page.wait({ selector: '[data-testid="primaryColumn"]' });
        // Step 2: Collect conversations with scroll-to-load
        const needed = maxSend + 10; // extra buffer for skips
        const convList = await page.evaluate(`(async () => {
      try {
        // Wait for initial items
        let attempts = 0;
        while (attempts < 10) {
          const items = document.querySelectorAll('[data-testid^="dm-conversation-item-"], [data-testid="conversation"]');
          if (items.length > 0) break;
          await new Promise(r => setTimeout(r, 1000));

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Open/start the browser session before running twitter reply-dm
  2. In batch scripts, assert the session is alive before each command and reopen if closed
  3. Catch CommandExecutionError with the 'Browser session required' message and restart the browser then rerun
  4. Avoid long gaps between session start and command execution so the session does not close

Example fix

// before
await cli.run('twitter reply-dm', { text: 'thanks!' });
// after
await cli.run('browser open');
await cli.run('twitter reply-dm', { text: 'thanks!' });
Defensive patterns

Strategy: validation

Validate before calling

if (!browserSession || !browserSession.page) {
  await cli.run('browser open');
}

Type guard

function hasLivePage(page) {
  return page != null && typeof page.evaluate === 'function' && typeof page.goto === 'function';
}

Try / catch

try {
  await cli.run('twitter reply-dm', kwargs);
} catch (e) {
  if (e instanceof CommandExecutionError && /Browser session required/.test(e.message)) {
    await cli.run('browser open');
    return cli.run('twitter reply-dm', kwargs);
  }
  throw e;
}

Prevention

When it happens

Trigger: Running 'twitter reply-dm' without an open browser session, so func receives page === undefined/null, before any navigation to the messages page.

Common situations: Scheduling the batch job without a browser bootstrap step; the browser crashed earlier in the pipeline; running the command from a script that never calls the session open command; session timed out and closed before this command.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/9280cda51cbffae1. Report an issue: GitHub.