jackwener/OpenCLI · error · CommandExecutionError

Browser session required for twitter unfollow

Error message

Browser session required for twitter unfollow

What it means

CommandExecutionError (code COMMAND_EXEC) thrown by `twitter unfollow` because unfollowing is a browser-write command needing an interactive page tied to the logged-in X session. When page is null the guard throws immediately rather than attempting the action. Identical guard pattern to twitter unblock and unbookmark.

Source

Thrown at clis/twitter/unfollow.js:17

import { cli, Strategy } from '@jackwener/opencli/registry';
import { CommandExecutionError, TimeoutError } from '@jackwener/opencli/errors';
cli({
    site: 'twitter',
    name: 'unfollow',
    access: 'write',
    description: 'Unfollow a Twitter user',
    domain: 'x.com',
    strategy: Strategy.UI,
    browser: true,
    args: [
        { name: 'username', type: 'string', positional: true, required: true, help: 'Twitter screen name (without @)' },
    ],
    columns: ['status', 'message'],
    func: async (page, kwargs) => {
        if (!page)
            throw new CommandExecutionError('Browser session required for twitter unfollow');
        const username = kwargs.username.replace(/^@/, '');
        await page.goto(`https://x.com/${username}`);
        await page.wait({ selector: '[data-testid="primaryColumn"]' });
        const result = await page.evaluate(`(async () => {
        let writeStarted = false;
        try {
            let attempts = 0;
            let unfollowBtn = null;

            while (attempts < 20) {
                // Check if already not following
                const followBtn = document.querySelector('[data-testid$="-follow"]');
                if (followBtn) {
                    return { ok: true, message: 'Not following @${username} (already unfollowed).' };
                }

                unfollowBtn = document.querySelector('[data-testid$="-unfollow"]');
                if (unfollowBtn) break;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Start/connect a browser session and log in to x.com, then re-run `twitter unfollow`
  2. Run the command in interactive mode so a page is passed to the command's func
  3. In scripts, check browser availability before invoking and fail with a clear prerequisite message

Example fix

// before
await cli.run(['twitter', 'unfollow', 'user']); // no browser session
// after
const page = await getBrowserPage('x.com');
if (!page) throw new Error('Start a browser session and log in to x.com first');
await cli.run(['twitter', 'unfollow', 'user']);
Defensive patterns

Strategy: validation

Validate before calling

if (!browserSession || !browserSession.page) {
  throw new Error('Start a browser session and log in to x.com before running twitter unfollow');
}

Type guard

function hasBrowserPage(ctx) { return ctx != null && typeof ctx.page === 'object' && ctx.page !== null; }

Try / catch

try {
  await cli.run(['twitter', 'unfollow', user]);
} catch (e) {
  if (e.code === 'COMMAND_EXEC' && /Browser session required/.test(e.message)) {
    console.error('Connect a browser session and log in to x.com first');
  } else throw e;
}

Prevention

When it happens

Trigger: Running `twitter unfollow <username>` without an active browser session, so `func` receives page === null/undefined at the guard on line 17.

Common situations: Scripted/headless invocation without connecting the browser; CI with no logged-in Chrome; forgetting the x.com login/bootstrap step before write commands.

Related errors


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