{"record":{"id":"3beda3ddc85bd8c1","repo":"can1357/oh-my-pi","slug":"element-handle-selector-no-longer-resolves","errorCode":null,"errorMessage":"Element handle selector no longer resolves","messagePattern":"Element handle selector no longer resolves","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts","lineNumber":806,"sourceCode":"\t}\n\n\tasync elementExists(selector: string): Promise<boolean> {\n\t\treturn await this.#selectorExists(this.#selectorSpec(selector));\n\t}\n\n\tasync elementBox(selector: string): Promise<BoundingBox | null> {\n\t\treturn await this.#selectorBox(this.#selectorSpec(selector));\n\t}\n\n\tasync evaluateOnSelector<TResult>(selector: string, source: string, args: unknown[]): Promise<TResult> {\n\t\tconst spec = this.#selectorSpec(selector);\n\t\tconst script = `(() => {\n\t\t\tconst spec = ${JSON.stringify(spec)};\n\t\t\tconst source = ${JSON.stringify(source)};\n\t\t\tconst args = ${JSON.stringify(args)};\n\t\t\t${PAGE_SELECTOR_HELPERS}\n\t\t\tconst element = findElement(spec);\n\t\t\tif (!element) throw new Error(\"Element handle selector no longer resolves\");\n\t\t\tconst callable = (0, eval)(\"(\" + source + \")\");\n\t\t\treturn callable(element, ...args);\n\t\t})()`;\n\t\t// Envelope so a stale selector or a throwing callback reports its actual\n\t\t// error instead of the daemon's generic js_error (see tab.evaluate()).\n\t\tconst result = (await this.#request(\"browser.eval\", {\n\t\t\tscript: serializeEvalWithEnvelope(script, []),\n\t\t})) as CmuxEvalResult;\n\t\treturn unwrapEvalEnvelope<TResult>(result.value, \"elementHandle.evaluate()\");\n\t}\n\n\tasync pageContent(): Promise<string> {\n\t\treturn await this.#evalScript<string>(\"document.documentElement.outerHTML\");\n\t}\n\n\tasync pageScreenshot(opts: ScreenshotOptions = {}): Promise<Buffer | string> {\n\t\tif (opts.selector) await this.scrollIntoView(opts.selector);\n\t\tconst result = await this.#captureScreenshotPng(this.#runContext?.timeoutMs ?? 30_000);","sourceCodeStart":788,"sourceCodeEnd":824,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts#L788-L824","documentation":"CmuxElementHandle invokes actions by re-resolving its stored selector inside the page (findElement(spec)); when the selector no longer matches any element in the live DOM, the page-side script throws 'Element handle selector no longer resolves', which surfaces as this ToolError. It means the element the handle was created from has been removed, re-rendered, or the selector text no longer matches.","triggerScenarios":"Using a CmuxElementHandle obtained via tab.waitFor()/tab.waitForSelector() after a framework re-render replaced the node; the DOM subtree was torn down (route change, modal close); the handle's selector relied on nth-position text/structure that changed; the page navigated away.","commonSituations":"React/Vue/Svelte keyed re-renders that recreate elements between waitFor and the action; handles held across await points while an animation swaps nodes; stale handles reused after waitForNavigation; auto-generated classes changing between renders when the selector was built from them.","solutions":["Re-acquire the handle right before use: call tab.waitForSelector(sel) again instead of reusing the old CmuxElementHandle.","Minimize the gap between obtaining the handle and acting on it — keep actions on the same element adjacent in code.","Use a more stable selector (data-testid, id, semantic role) so re-resolution survives re-renders.","If the page navigates or mutates heavily, perform the action inside tab.evaluate() against a freshly queried element."],"exampleFix":"// before: stale handle reused after re-render\nconst btn = await tab.waitForSelector(\"button.submit\");\nawait someAsyncWork();\nawait btn.click(); // selector no longer resolves\n// after: re-resolve just before acting\nawait someAsyncWork();\nconst btn = await tab.waitForSelector(\"button.submit\");\nawait btn.click();","handlingStrategy":"retry","validationCode":"// re-verify the selector still resolves before using the handle\nconst stillThere = await tab.evaluate(\n  (sel) => !!document.querySelector(sel),\n  selector,\n);\nif (!stillThere) await tab.waitForSelector(selector);","typeGuard":null,"tryCatchPattern":"try {\n  await elementHandle.click();\n} catch (err) {\n  if (err instanceof ToolError && err.message.includes(\"no longer resolves\")) {\n    const fresh = await tab.waitForSelector(selector, { timeout: 10_000 });\n    await fresh.click();\n  } else throw err;\n}","preventionTips":["Re-acquire handles immediately before acting instead of caching them across awaits","Prefer stable selectors (data-testid, id) over generated classes and nth-position","Refresh handles after any navigation or known re-render boundary","Keep the handle→action gap free of awaits that can trigger framework updates"],"tags":["browser","stale-element","dom","selector"],"backgroundTag":"stale-element-reference","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}