jackwener/OpenCLI · error · CommandExecutionError

Failed to remove @${username} from list ${listId}: member_co

Error message

Failed to remove @${username} from list ${listId}: member_count unchanged (${memberCountBefore} → ${memberCountAfter}).

What it means

The library found the target list in the post-delete payload and compared member counts: the count did not decrease (equal or higher), meaning the Save click did not actually remove the user. The removal failed silently in the UI, so the library throws instead of reporting success.

Source

Thrown at clis/twitter/list-remove-core.js:257

                if (!r.ok) return { __error: 'HTTP ' + r.status };
                return await r.json();
            }`));
            if (listsAfter && listsAfter.__error) {
                throw new CommandExecutionError(`Could not verify list removal: ${listsAfter.__error}`);
            }
            if (!getListsManagementInstructions(listsAfter)) {
                throw new CommandExecutionError('Could not verify list removal: unexpected lists payload shape');
            }
            const parsedAfter = parseListsManagement(listsAfter, new Set());
            const afterList = parsedAfter.find((l) => l.id === listId);
            if (!afterList) {
                throw new CommandExecutionError(`Could not verify list removal: list ${listId} missing from post-delete payload`);
            }
            const memberCountAfter = Number(afterList.members) || 0;
            if (memberCountAfter < memberCountBefore) {
                verifiedBy = `member_count ${memberCountBefore} → ${memberCountAfter}`;
            } else {
                throw new CommandExecutionError(`Failed to remove @${username} from list ${listId}: member_count unchanged (${memberCountBefore} → ${memberCountAfter}).`);
            }
        }

    return [{
        listId,
        username,
        userId: String(userId),
        status: uiResult.noop ? 'noop' : 'success',
        message: uiResult.noop
            ? `@${username} was not a member of list ${listId}`
            : `Removed @${username} from list ${listId} (verified via ${verifiedBy})`,
    }];
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Simply retry the command — most of these are transient timing races; the next run re-scrapes fresh coordinates
  2. Slow things down / use a machine with less load, and avoid scrolling or interacting with the browser during the 800ms/3500ms waits
  3. Check whether the target user is still a member with `opencli twitter lists` / list members; remove manually if the UI keeps failing
  4. Update the Chrome extension and opencli — older nativeClick versions can drop synthesized clicks
  5. If persistent, the toggle likely opened the profile: report the coordinate-click flow as broken for the current X DOM

Example fix

// before
await listRemoveUser(page, { listId, username });
// after
let done = false;
for (let i = 0; i < 2 && !done; i++) {
  try { await listRemoveUser(page, { listId, username }); done = true; }
  catch (e) {
    if (!/member_count unchanged/.test(e.message)) throw e;
    await new Promise(r => setTimeout(r, 5000));
  }
}
if (!done) console.error('Removal failed twice; check list membership manually.');
Defensive patterns

Strategy: retry

Validate before calling

const lists = await run('twitter', 'lists', {});
const target = lists.find(l => String(l.id) === String(listId));
if (!target) throw new Error(`List ${listId} not found.`);
const before = Number(target.members) || 0;
// capture before-count so you can detect unchanged state yourself

Try / catch

for (let attempt = 0; attempt < 2; attempt++) {
  try { await listRemoveUser(page, { listId, username }); break; }
  catch (e) {
    if (!/member_count unchanged/.test(e.message) || attempt === 1) throw e;
    await new Promise(r => setTimeout(r, 5000));
  }
}

Prevention

When it happens

Trigger: nativeClick on the row toggled membership but the Save click was missed/mis-timed (3500ms wait hit a still-saving dialog), or the row click opened the user profile instead of toggling, so the member_count after equals the count before.

Common situations: Slow rendering so the second click landed before the toggle registered; the coordinates drifted because the dialog scrolled or re-laid out between scrape and click; the extension's nativeClick was intercepted by an X overlay (toast, tooltip); rate-limited actions accepted by UI but rejected server-side.

Related errors


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