{"record":{"id":"d7d80e3d0746bbf2","repo":"jackwener/OpenCLI","slug":"codex-picker-option-disappeared-before-it-could-be","errorCode":null,"errorMessage":"Codex picker option disappeared before it could be clicked.","messagePattern":"Codex picker option disappeared before it could be clicked\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/codex/send.js","lineNumber":187,"sourceCode":"        bubbles: true, cancelable: true, button: 0, buttons: 1,\n        clientX: Math.round(rect.left + rect.width / 2),\n        clientY: Math.round(rect.top + rect.height / 2),\n      };\n      // The picker only responds to the full pointer chain, and deferring it\n      // keeps the eval response ahead of the re-render that closes the picker.\n      Promise.resolve().then(() => {\n        try {\n          target.dispatchEvent(new PointerEvent('pointerdown', { ...init, pointerType: 'mouse' }));\n          target.dispatchEvent(new MouseEvent('mousedown', init));\n          target.dispatchEvent(new PointerEvent('pointerup', { ...init, pointerType: 'mouse' }));\n          target.dispatchEvent(new MouseEvent('mouseup', init));\n          target.dispatchEvent(new MouseEvent('click', init));\n        } catch {}\n      });\n      return true;\n    })()`));\n            if (clicked !== true) {\n                throw new CommandExecutionError('Codex picker option disappeared before it could be clicked.', `wanted=${picked.title}`);\n            }\n            // The click above is deferred, so verify it landed: the picker must\n            // close and the composer must hold a chip for the picked option.\n            let applied = null;\n            for (let attempt = 0; attempt < 20; attempt += 1) {\n                const state = await readComposerState(page);\n                const pickerOpen = unwrapEvaluateResult(await page.evaluate(`Array.from(document.querySelectorAll('${PICKER_ITEM_SELECTOR}')).some((it) => it instanceof HTMLElement && !!it.offsetParent)`)) === true;\n                if (!pickerOpen && state.chips.some((chip) => chipMatchesLabel(chip, picked.title))) {\n                    applied = state;\n                    break;\n                }\n                applied = state;\n                await page.wait(0.1);\n            }\n            if (!applied || !applied.chips.some((chip) => chipMatchesLabel(chip, picked.title))) {\n                throw new CommandExecutionError(\n                    'Codex picker selection did not reach the composer.',\n                    `expected=${picked.title} ${describeComposerState(applied ?? { text: '', chips: [] })}`,","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/codex/send.js#L169-L205","documentation":"This error is thrown when a Codex slash-command picker option that was found in an earlier evaluation pass disappears from the DOM before the deferred, injected click can execute. The library clicks picker options via an in-page script that re-locates the element at click time; if that re-lookup fails, `clicked` is not `true` and this error is raised, aborting the send so a stale click cannot target the wrong option.","triggerScenarios":"The in-page click script returned a value other than `true` — the picker option element matching `picked.title` was removed, re-rendered, or the picker closed between the option-discovery pass and the deferred click (e.g. Codex re-rendered the dropdown, an animation removed the row, or the DOM mutated in between).","commonSituations":"Slow or busy Codex session where the picker auto-closes; Codex UI updates that re-render the picker list mid-click; running the send while another automation step already dismissed the picker; flaky DOM timing on a heavily loaded page.","solutions":["Re-run the command; the error is timing-related and a retry usually finds the picker stable.","Ensure no other automation (keyboard input, Escape presses) dismisses the picker concurrently.","Slow down or run against a less loaded Codex page so re-renders don't race the click.","Update the library/Codex if the picker DOM structure changed and re-lookup selectors no longer match."],"exampleFix":"// before (racing click)\nconst clicked = await page.evaluate(`(async () => { ... locate + click ... })()`);\n// after (verify picker still open right before clicking, e.g. re-run with a small stabilization wait)\nawait page.wait(0.2);\nconst clicked = await page.evaluate(`(async () => { ... re-locate option; if (!option) return false; option.dispatchEvent(new MouseEvent('click', init)); return true; })()`);","handlingStrategy":"retry","validationCode":"// Before invoking send, confirm the picker is open and the option exists\nconst optionPresent = await page.evaluate(`!!document.querySelector('[role=\"menu\"] [role=\"menuitem\"], [role=\"listbox\"] [role=\"option\"]')`);\nif (!optionPresent) { /* reopen picker or abort before calling send */ }","typeGuard":"const isClickResult = (v) => v === true;","tryCatchPattern":"try {\n  await codexSend({ pick: label });\n} catch (e) {\n  if (String(e.message).includes('picker option disappeared')) {\n    await page.pressKey('Escape');\n    await retry(() => codexSend({ pick: label }), { attempts: 3, backoff: 250 });\n  } else throw e;\n}","preventionTips":["Avoid concurrent automation that can dismiss the picker mid-click.","Run against a settled, fully loaded Codex page.","Retry once or twice — the failure is a timing race.","Keep the library updated after Codex UI changes."],"tags":["codex","picker","dom-race","automation"],"backgroundTag":"element-detached-before-click","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}