{"record":{"id":"c91ed11d42f4b3ac","repo":"garrytan/gstack","slug":"ref-selector-not-found-run-snapshot-to-get-f","errorCode":null,"errorMessage":"Ref ${selector} not found. Run 'snapshot' to get fresh refs.","messagePattern":"Ref (.+?) not found\\. Run 'snapshot' to get fresh refs\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"browse/src/tab-session.ts","lineNumber":93,"sourceCode":"  // ─── Ref Map ──────────────────────────────────────────────\n  setRefMap(refs: Map<string, RefEntry>) {\n    this.refMap = refs;\n  }\n\n  clearRefs() {\n    this.refMap.clear();\n  }\n\n  /**\n   * Resolve a selector that may be a @ref (e.g., \"@e3\", \"@c1\") or a CSS selector.\n   * Returns { locator } for refs or { selector } for CSS selectors.\n   */\n  async resolveRef(selector: string): Promise<{ locator: Locator } | { selector: string }> {\n    if (selector.startsWith('@e') || selector.startsWith('@c')) {\n      const ref = selector.slice(1); // \"e3\" or \"c1\"\n      const entry = this.refMap.get(ref);\n      if (!entry) {\n        throw new Error(\n          `Ref ${selector} not found. Run 'snapshot' to get fresh refs.`\n        );\n      }\n      const count = await entry.locator.count();\n      if (count === 0) {\n        throw new Error(\n          `Ref ${selector} (${entry.role} \"${entry.name}\") is stale — element no longer exists. ` +\n          `Run 'snapshot' for fresh refs.`\n        );\n      }\n      return { locator: entry.locator };\n    }\n    return { selector };\n  }\n\n  /** Get the ARIA role for a ref selector, or null for CSS selectors / unknown refs. */\n  getRefRole(selector: string): string | null {\n    if (selector.startsWith('@e') || selector.startsWith('@c')) {","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/browse/src/tab-session.ts#L75-L111","documentation":"Thrown by TabSession.resolveRef when a selector starting with `@e` or `@c` does not match any key in the current refMap. The refMap is populated by the most recent `snapshot` call and cleared on main-frame navigation; refs are short-lived identifiers, not persistent CSS selectors.","triggerScenarios":"Calling click/type/scroll/fill with a ref before running `snapshot`; calling it after a navigation that cleared the refMap; using a ref like `@e99` that was never issued; misspelling the ref (`@e1` vs `@E1` — case sensitive).","commonSituations":"A long agent session where the page auto-navigated between the snapshot and the action; copy-pasting a ref from an old snapshot output; switching tabs and forgetting refs are per-tab.","solutions":["Run `snapshot` again and re-read the ref list before retrying the action.","Switch to a CSS selector if you need a stable target that survives navigation.","Confirm you are operating on the same tab that produced the snapshot.","For scripted flows, call snapshot immediately before each action rather than caching refs across navigations."],"exampleFix":"// before\nawait click(session, '@e5'); // refMap cleared by nav\n// after\nawait snapshot(session); // refresh refs\nawait click(session, '@e5');","handlingStrategy":"retry","validationCode":"function refExists(session: TabSession, selector: string): boolean {\n  if (!selector.startsWith('@e') && !selector.startsWith('@c')) return true; // CSS — out of scope\n  const ref = selector.slice(1);\n  return session.getRefCount() > 0 && session.getRefEntries().some(e => e.ref === ref);\n}","typeGuard":"function isKnownRef(session: TabSession, sel: string): boolean {\n  if (!/^@[ec]/.test(sel)) return true;\n  return session.getRefEntries().some(e => e.ref === sel.slice(1));\n}","tryCatchPattern":"async function clickSafe(session: TabSession, sel: string) {\n  try { return await click(session, sel); }\n  catch (e: any) {\n    if (/^Ref .* not found/.test(e.message)) {\n      await snapshot(session); // refresh refs\n      return click(session, sel);\n    }\n    throw e;\n  }\n}","preventionTips":["Always run snapshot before any @e/@c action in the same tab.","Re-snapshot after every navigation, including SPA route changes.","If you need a stable target, switch to a CSS selector with data-testid.","Keep refs scoped per tab — they do not transfer across tabs."],"tags":["refs","snapshot","stale-state","navigation","tab-session"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}