{"record":{"id":"62dabc374edf3441","repo":"jackwener/OpenCLI","slug":"failed-to-remove-username-from-list-listid","errorCode":null,"errorMessage":"Failed to remove @${username} from list ${listId}: ${uiResult.message}","messagePattern":"Failed to remove @(.+?) from list (.+?): (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/list-remove-core.js","lineNumber":221,"sourceCode":"                    return /^(Save|Done|保存|完成|儲存)$/i.test(txt);\n                });\n                const saveRect = saveButton ? saveButton.getBoundingClientRect() : null;\n                return {\n                    ok: true,\n                    needsNativeInteraction: true,\n                    rowClickX: Math.round(rowRect.left + rowRect.width / 2),\n                    rowClickY: Math.round(rowRect.top + rowRect.height / 2),\n                    saveClickX: saveRect ? Math.round(saveRect.left + saveRect.width / 2) : null,\n                    saveClickY: saveRect ? Math.round(saveRect.top + saveRect.height / 2) : null,\n                    mutationsBefore: window.__opencliListMutations.length,\n                };\n            } catch (e) {\n                return { ok: false, message: 'UI error: ' + (e?.message || String(e)) };\n            }\n        })()`));\n\n        if (!uiResult.ok) {\n            throw new CommandExecutionError(`Failed to remove @${username} from list ${listId}: ${uiResult.message}`);\n        }\n\n        let verifiedBy = null;\n        if (uiResult.needsNativeInteraction) {\n            if (typeof page.nativeClick !== 'function') {\n                throw new CommandExecutionError('Requires up-to-date Chrome extension (nativeClick).');\n            }\n            if (!uiResult.saveClickX) {\n                throw new CommandExecutionError('Save button not found in dialog.');\n            }\n            const memberCountBefore = Number(targetList.members) || 0;\n            await page.nativeClick(uiResult.rowClickX, uiResult.rowClickY);\n            await new Promise((r) => setTimeout(r, 800));\n            await page.nativeClick(uiResult.saveClickX, uiResult.saveClickY);\n            await new Promise((r) => setTimeout(r, 3500));\n            const listsAfter = unwrapBrowserResult(await page.evaluate(`async () => {\n                const r = await fetch(${JSON.stringify(listsUrl)}, { headers: ${headers}, credentials: 'include' });\n                if (!r.ok) return { __error: 'HTTP ' + r.status };","sourceCodeStart":203,"sourceCodeEnd":239,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/list-remove-core.js#L203-L239","documentation":"The actual removal is attempted through the x.com UI inside the page (opening the member dialog, clicking the remove control). The in-page script reports failure as { ok: false, message }, and this CommandExecutionError wraps that message. It means the DOM-driven removal flow did not complete — a selector went missing, a click did not land, or the page threw inside evaluate.","triggerScenarios":"The in-page UI routine throws (wrapped as 'UI error: ...'), the list member row or remove/confirm buttons are not found in the dialog, an unexpected overlay (login prompt, rate-limit notice, JS error dialog) intercepts the flow, or x.com's DOM changed so data-testid selectors no longer match.","commonSituations":"x.com shipped a UI redesign changing data-testid attributes; slow page load left the dialog half-rendered when clicking; a modal/interstitial (login, sensitive-content, outage banner) blocked interaction; the account got logged out mid-flow; network slowness caused element detach between locate and click.","solutions":["Read uiResult.message in the error text: selector-not-found → likely a DOM/testid change; 'UI error: ...' → JS exception in the page.","Reload x.com in the controlled browser and confirm you are still logged in, then retry.","Update the data-testid selectors in the in-page UI script to match the current x.com member-dialog markup.","Retry when the network/site is not slow — transient render races resolve on a second attempt.","Dismiss any overlay/interstitial dialogs shown on x.com before running the command."],"exampleFix":"// before (row selector outdated after x.com redesign)\nconst row = document.querySelector(`[data-testid=\"UserCell\"]`);\n// after\nconst row = document.querySelector('[data-testid=\"ListItem\"], [data-testid=\"UserCell\"]'); // support both old and new markup","handlingStrategy":"try-catch","validationCode":"// Ensure the profile page is interactive before invoking the UI flow\nawait page.goto(`https://x.com/${username}`);\nawait page.wait({ selector: '[data-testid=\"primaryColumn\"]' });\nconst overlay = await page.evaluate(\"document.querySelector('[data-testid=\"sheetDialog\"], [aria-label=\"Log in\"]')\");\nif (overlay) throw new Error('Blocking overlay present on x.com; resolve it first');","typeGuard":"function uiResultOk(r) {\n  return typeof r === 'object' && r !== null && r.ok === true;\n}","tryCatchPattern":"try {\n  await listRemoveUser(page, { listId, username });\n} catch (e) {\n  if (e instanceof CommandExecutionError && e.message.startsWith('Failed to remove')) {\n    console.warn('UI flow failed:', e.message);\n    // reload page, dismiss overlays, update selectors, retry once\n  } else throw e;\n}","preventionTips":["Pin/update selector expectations after x.com UI releases.","Run against a clean, logged-in profile with no blocking overlays.","Allow generous waits so the dialog fully renders before clicks.","Retry once on transient render races before escalating."],"tags":["twitter","ui-automation","dom-selector","browser-automation"],"backgroundTag":"dom-selector-not-found","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}