{"record":{"id":"ab483b15688f6fc3","repo":"can1357/oh-my-pi","slug":"unknown-element-id-id-run-tab-observe-to-ref","errorCode":null,"errorMessage":"Unknown element id ${id}. Run tab.observe() to refresh the element list.","messagePattern":"Unknown element id (.+?)\\. Run tab\\.observe\\(\\) to refresh the element list\\.","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/browser/tab-worker.ts","lineNumber":2123,"sourceCode":"\n\tasync #waitForResponse(\n\t\tpattern: string | RegExp | ((response: HTTPResponse) => boolean | Promise<boolean>),\n\t\ttimeout: number,\n\t\tsignal: AbortSignal,\n\t): Promise<HTTPResponse> {\n\t\tconst page = this.#requirePage();\n\t\tconst predicate: (response: HTTPResponse) => boolean | Promise<boolean> =\n\t\t\ttypeof pattern === \"function\"\n\t\t\t\t? pattern\n\t\t\t\t: pattern instanceof RegExp\n\t\t\t\t\t? response => pattern.test(response.url())\n\t\t\t\t\t: response => response.url().includes(pattern);\n\t\treturn (await untilAborted(signal, () => page.waitForResponse(predicate, { timeout, signal }))) as HTTPResponse;\n\t}\n\n\tasync #resolveCachedHandle(id: number): Promise<ElementHandle> {\n\t\tconst handle = this.#elementCache.get(id);\n\t\tif (!handle) throw new ToolError(`Unknown element id ${id}. Run tab.observe() to refresh the element list.`);\n\t\ttry {\n\t\t\tconst isConnected = (await handle.evaluate(el => el.isConnected)) as boolean;\n\t\t\tif (!isConnected) {\n\t\t\t\tthis.#clearElementCache();\n\t\t\t\tthrow new ToolError(`Element id ${id} is stale. Run tab.observe() again.`);\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tif (err instanceof ToolError) throw err;\n\t\t\tthis.#clearElementCache();\n\t\t\tthrow new ToolError(`Element id ${id} is stale. Run tab.observe() again.`);\n\t\t}\n\t\treturn handle;\n\t}\n\n\tasync #resolveAriaRef(id: string): Promise<ElementHandle> {\n\t\tconst ref = parseAriaRefSelector(id) ?? id.trim();\n\t\tconst handle = await resolveAriaRefHandle(this.#requirePage(), ref);\n\t\tif (!handle) {","sourceCodeStart":2105,"sourceCodeEnd":2141,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/browser/tab-worker.ts#L2105-L2141","documentation":"Interactions can reference cached elements by numeric id from the last observe(). #resolveCachedHandle looks the id up in #elementCache; an unknown id (never observed, out of range, or cache cleared by a new observe) throws this ToolError telling the user to re-observe. The cache is not auto-refreshed because element lists can be large and refreshes are expensive.","triggerScenarios":"Calling an action with an element id that was never returned by tab.observe(), an id beyond the current list, or an id invalidated because another observe() cleared and rebuilt #elementCache.","commonSituations":"LLM/agent hallucinating an id or reusing ids from a previous observe after the list shifted; two clients sharing one tab where each observe() invalidates the other's ids; ids from a different tab's observation.","solutions":["Run tab.observe() to get the current element list, then use ids from that fresh output.","Retry the action with the corrected id — check the id is in the latest observe result.","If observing frequently invalidates ids, snapshot the needed selector/aria-ref strings instead of ids.","Avoid interleaving observes from multiple consumers on the same tab."],"exampleFix":"// before\nawait tab.click(42); // id 42 from an older observe\n// after\nconst { elements } = await tab.observe();\nconst target = elements.find(e => e.name === \"Submit\");\nawait tab.click(target.id);","handlingStrategy":"try-catch","validationCode":"const known = latestObservation.elements.some(e => e.id === id);\nif (!known) throw new Error(`id ${id} not in latest observe() output; re-observe first`);","typeGuard":null,"tryCatchPattern":"try {\n  await tab.click(id);\n} catch (err) {\n  if (err.message.startsWith(\"Unknown element id\")) {\n    const fresh = await tab.observe();\n    const el = fresh.elements.find(e => e.name === expectedName);\n    if (el) return tab.click(el.id);\n  }\n  throw err;\n}","preventionTips":["Always use ids from the most recent observe() output","Never reuse ids across observes or tabs","Re-observe after any navigation or heavy DOM change","Store selectors/refs instead of ids for long-lived automation"],"tags":["browser","cache","element-id","stale-reference"],"backgroundTag":"stale-element-reference","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}