jackwener/OpenCLI · error · CommandExecutionError
Failed to delete list ${listId}: ${deleteResult?.message ||
Error message
Failed to delete list ${listId}: ${deleteResult?.message || 'unknown UI failure'} What it means
The twitter list-delete command drives x.com's web UI via in-page DOM automation to delete a list. When the scripted UI flow fails to reach a successful end state (any of its findButton/click steps returns { ok: false } with a message, or the browser result itself is unusable), it throws CommandExecutionError wrapping that message. This throw at list-delete.js:148 means the click-through did NOT complete — the verification step (list still present afterwards) was never reached.
Source
Thrown at clis/twitter/list-delete.js:148
const editLink = Array.from(document.querySelectorAll('a[href$="/info"]'))
.find((el) => visible(el) && /edit list/i.test(el.innerText || el.textContent || ''));
if (!editLink) return { ok: false, message: 'Edit List link not found' };
editLink.click();
const editDialog = await waitFor(() => document.querySelector('[role="dialog"]'));
if (!editDialog) return { ok: false, message: 'Edit List dialog did not open' };
const deleteButton = findButton('Delete List');
if (!deleteButton) return { ok: false, message: 'Delete List button not found' };
deleteButton.click();
await sleep(800);
const confirmButton = document.querySelector('[data-testid="confirmationSheetConfirm"]')
|| findButton('Delete');
if (!confirmButton) return { ok: false, message: 'Delete confirmation button not found' };
confirmButton.click();
await sleep(2500);
return { ok: true, url: location.href };
})()`));
if (!deleteResult?.ok) {
throw new CommandExecutionError(`Failed to delete list ${listId}: ${deleteResult?.message || 'unknown UI failure'}`);
}
const listsAfter = await getManagedLists(page, headers);
if (listsAfter.some((list) => list.id === listId)) {
throw new CommandExecutionError(`Failed to delete list ${listId}: list still appears in managed lists.`);
}
return [buildListDeleteRow({ listId, targetList })];
},
});
View on GitHub (pinned to 49907e53dc)
Solutions
- Re-run the command once — transient slow rendering often causes the dialog/button waits to time out
- Verify the listId is a list YOUR account owns by checking it appears in `opencli twitter lists`; only owned lists show the Edit List link
- Confirm you are on the English UI (button matching is text-based: 'Edit List', 'Delete List', 'Delete')
- Check x.com manually at https://x.com/i/lists/<listId> — if the UI changed, the selectors in list-delete.js need updating
- If deleteResult was null ('unknown UI failure'), check for page.evaluate/unwrapBrowserResult errors or headless-browser exceptions in earlier logs
Example fix
// before (fragile text-based matching)
const confirmButton = document.querySelector('[data-testid="confirmationSheetConfirm"]') || findButton('Delete');
// after (increase wait and add fallback waiting for confirm sheet instead of fixed 800ms sleep)
await sleep(1500);
const confirmButton = await waitFor(() => document.querySelector('[data-testid="confirmationSheetConfirm"]') || findButton('Delete'), { timeoutMs: 10000 }); Defensive patterns
Strategy: try-catch
Validate before calling
// Before running, confirm the list exists and is yours:
const rows = await run('opencli twitter lists');
const list = rows.find(r => r.listId === listId);
if (!list) throw new Error(`List ${listId} not in your managed lists; delete will fail in the UI flow`); Type guard
function isUiDeleteResult(r) {
return r !== null && typeof r === 'object' && typeof r.ok === 'boolean' && (r.ok === false ? typeof r.message === 'string' : true);
} Try / catch
try {
await run(`opencli twitter list-delete ${listId} --confirm true`);
} catch (e) {
if (/Failed to delete list .*: (Edit List link not found|Edit List dialog did not open|Delete List button not found|Delete confirmation button not found)/.test(e.message)) {
// transient UI/timing issue: retry once, else inspect selectors
} else if (/unknown UI failure/.test(e.message)) {
// browser evaluate returned unusable data: check headless logs
}
throw e;
} Prevention
- Run `opencli twitter lists` first to confirm the listId exists and you own it
- Retry once on first failure — most hits are timing/render flakiness
- Keep the automation browser on the English UI so text-based button matching works
- After an error, open x.com/i/lists/<id> manually to see whether the UI changed
- Watch for X.com front-end updates that rename 'Delete List' or the confirmationSheetConfirm testid
When it happens
Trigger: Any in-page failure inside the page.evaluate block on x.com/i/lists/<listId>: (1) the 'Edit List' link is not found (list page didn't render, wrong listId, layout changed); (2) the edit [role=dialog] never opens within 10s; (3) no visible 'Delete List' button in the dialog; (4) no '[data-testid=confirmationSheetConfirm]' or 'Delete' confirmation button appears after 800ms; (5) deleteResult is null/undefined after unwrapBrowserResult, yielding the 'unknown UI failure' fallback message.
Common situations: Passing a listId the account can see but not own (no Edit List link); X.com UI A/B changes renaming buttons or testids (e.g. confirmationSheetConfirm); slow page loads under flaky network so the dialog wait times out; running against a locale where button text isn't 'Delete List' in English; page.evaluate returning a wrapped/error result so deleteResult is undefined.
Related errors
- Nothing changed. Open the tweet in the browser and retry.
- Nothing changed. Open the tweet in the browser and retry.
- Could not open the reply composer.
- Unexpected 12306 probe: ${JSON.stringify(probe)}
- Waiting for 12306 tk auth cookie
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/ba167c1d4f98c36a.
Report an issue: GitHub.