{"record":{"id":"5fa435d95c1b5cd5","repo":"jackwener/OpenCLI","slug":"failed-to-remove-username-from-list-listid-5fa435","errorCode":null,"errorMessage":"Failed to remove @${username} from list ${listId}: member_count unchanged (${memberCountBefore} → ${memberCountAfter}).","messagePattern":"Failed to remove @(.+?) from list (.+?): member_count unchanged \\((.+?) → (.+?)\\)\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/list-remove-core.js","lineNumber":257,"sourceCode":"                if (!r.ok) return { __error: 'HTTP ' + r.status };\n                return await r.json();\n            }`));\n            if (listsAfter && listsAfter.__error) {\n                throw new CommandExecutionError(`Could not verify list removal: ${listsAfter.__error}`);\n            }\n            if (!getListsManagementInstructions(listsAfter)) {\n                throw new CommandExecutionError('Could not verify list removal: unexpected lists payload shape');\n            }\n            const parsedAfter = parseListsManagement(listsAfter, new Set());\n            const afterList = parsedAfter.find((l) => l.id === listId);\n            if (!afterList) {\n                throw new CommandExecutionError(`Could not verify list removal: list ${listId} missing from post-delete payload`);\n            }\n            const memberCountAfter = Number(afterList.members) || 0;\n            if (memberCountAfter < memberCountBefore) {\n                verifiedBy = `member_count ${memberCountBefore} → ${memberCountAfter}`;\n            } else {\n                throw new CommandExecutionError(`Failed to remove @${username} from list ${listId}: member_count unchanged (${memberCountBefore} → ${memberCountAfter}).`);\n            }\n        }\n\n    return [{\n        listId,\n        username,\n        userId: String(userId),\n        status: uiResult.noop ? 'noop' : 'success',\n        message: uiResult.noop\n            ? `@${username} was not a member of list ${listId}`\n            : `Removed @${username} from list ${listId} (verified via ${verifiedBy})`,\n    }];\n}\n","sourceCodeStart":239,"sourceCodeEnd":271,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/list-remove-core.js#L239-L271","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Simply retry the command — most of these are transient timing races; the next run re-scrapes fresh coordinates","Slow things down / use a machine with less load, and avoid scrolling or interacting with the browser during the 800ms/3500ms waits","Check whether the target user is still a member with `opencli twitter lists` / list members; remove manually if the UI keeps failing","Update the Chrome extension and opencli — older nativeClick versions can drop synthesized clicks","If persistent, the toggle likely opened the profile: report the coordinate-click flow as broken for the current X DOM"],"exampleFix":"// before\nawait listRemoveUser(page, { listId, username });\n// after\nlet done = false;\nfor (let i = 0; i < 2 && !done; i++) {\n  try { await listRemoveUser(page, { listId, username }); done = true; }\n  catch (e) {\n    if (!/member_count unchanged/.test(e.message)) throw e;\n    await new Promise(r => setTimeout(r, 5000));\n  }\n}\nif (!done) console.error('Removal failed twice; check list membership manually.');","handlingStrategy":"retry","validationCode":"const lists = await run('twitter', 'lists', {});\nconst target = lists.find(l => String(l.id) === String(listId));\nif (!target) throw new Error(`List ${listId} not found.`);\nconst before = Number(target.members) || 0;\n// capture before-count so you can detect unchanged state yourself","typeGuard":null,"tryCatchPattern":"for (let attempt = 0; attempt < 2; attempt++) {\n  try { await listRemoveUser(page, { listId, username }); break; }\n  catch (e) {\n    if (!/member_count unchanged/.test(e.message) || attempt === 1) throw e;\n    await new Promise(r => setTimeout(r, 5000));\n  }\n}","preventionTips":["Don't interact with the browser during the click sequence (800ms/3500ms waits are timing-sensitive)","Retry once automatically — races are usually transient","Keep the extension and library current (nativeClick reliability)","After failures, check actual membership via the lists/members API instead of assuming either way"],"tags":["ui-automation","click-coordinates","verification-failed"],"backgroundTag":"ui-automation-click-missed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}