{"record":{"id":"c6e78a8dc50347df","repo":"n8n-io/n8n","slug":"stale-element-ref-ref","errorCode":null,"errorMessage":"Stale element ref: ${ref}","messagePattern":"Stale element ref: (.+?)","errorType":"exception","errorClass":"StaleRefError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/mcp-browser/src/adapters/playwright.ts","lineNumber":885,"sourceCode":"\t\t\tconst html = await locator.evaluate((el) => el.outerHTML);\n\t\t\treturn { html, url: page.url() };\n\t\t}\n\n\t\treturn { html: await page.content(), url: page.url() };\n\t}\n\n\t// =========================================================================\n\t// Ref resolution — uses Playwright's built-in aria-ref selector engine\n\t// =========================================================================\n\n\tasync resolveRef(pageId: string, ref: string): Promise<unknown> {\n\t\tconst { page } = await this.ensurePage(pageId);\n\t\tconst locator = page.locator(`aria-ref=${ref}`);\n\n\t\t// Verify the element exists\n\t\ttry {\n\t\t\tconst count = await locator.count();\n\t\t\tif (count === 0) throw new StaleRefError(ref);\n\t\t} catch (error) {\n\t\t\tif (error instanceof StaleRefError) throw error;\n\t\t\tthrow new StaleRefError(ref);\n\t\t}\n\n\t\treturn locator;\n\t}\n\n\t// =========================================================================\n\t// Private helpers\n\t// =========================================================================\n\n\tprivate requireContext(): BrowserContext {\n\t\tif (!this.context) throw new Error('Browser context not initialized');\n\t\treturn this.context;\n\t}\n\n\tprivate requirePage(pageId: string): PageState {","sourceCodeStart":867,"sourceCodeEnd":903,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/mcp-browser/src/adapters/playwright.ts#L867-L903","documentation":"StaleRefError is thrown by PlaywrightAdapter.resolveRef when an aria-ref selector resolves to zero elements on the page. The adapter builds a locator with `aria-ref=${ref}` and calls locator.count(); a count of 0 means the ref no longer points to a live DOM node. Refs are only valid against the snapshot that produced them, so any DOM mutation between snapshot and action invalidates them.","triggerScenarios":"Calling any browser interaction tool (click, type, fill) with a ref obtained from an older browser_snapshot after the page has navigated, reloaded, or been mutated by JS. Also triggered when a ref was copied from a different page/tab than the active one.","commonSituations":"Agent takes a snapshot, performs an action that triggers navigation (form submit, SPA route change), then tries to reuse the old ref. Refs are also invalidated by lazy-loaded content replacing nodes, or by switching activePageId without refreshing refs.","solutions":["Call browser_snapshot again on the same pageId and use the freshly returned refs for the next action.","Verify the pageId passed to the action matches the pageId used for the snapshot that generated the ref.","After any tool call that can navigate, re-snapshot before issuing element-targeted actions."],"exampleFix":"// before — ref is stale after navigation\nawait adapter.click('page-1', { ref: 'e12' }); // throws StaleRefError\n\n// after — refresh snapshot, use new ref\nconst snap = await snapshot('page-1');\nawait adapter.click('page-1', { ref: snap.refs[0] });","handlingStrategy":"retry","validationCode":"// Before acting on a ref, verify it still resolves.\nconst count = await page.locator(`aria-ref=${ref}`).count();\nif (count === 0) { const snap = await snapshot(pageId); ref = pickFreshRef(snap); }","typeGuard":"function isFreshRef(snap: Snapshot, ref: string): boolean {\n  return snap.elements.some((e) => e.ref === ref);\n}","tryCatchPattern":"try {\n  await adapter.click(pageId, { ref });\n} catch (e) {\n  if (e instanceof StaleRefError) {\n    const snap = await snapshot(pageId);\n    await adapter.click(pageId, { ref: pickFreshRef(snap) });\n  } else throw e;\n}","preventionTips":["Always re-snapshot after any tool call that may navigate or mutate the DOM.","Keep refs scoped to a single pageId — never reuse across tabs.","Treat refs as single-use; capture, act, discard."],"tags":["stale-ref","playwright","aria-ref","snapshot"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}