garrytan/gstack · error · Error

Ref ${selector} not found. Run 'snapshot' to get fresh refs.

Error message

Ref ${selector} not found. Run 'snapshot' to get fresh refs.

What it means

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.

Source

Thrown at browse/src/tab-session.ts:93

  // ─── Ref Map ──────────────────────────────────────────────
  setRefMap(refs: Map<string, RefEntry>) {
    this.refMap = refs;
  }

  clearRefs() {
    this.refMap.clear();
  }

  /**
   * Resolve a selector that may be a @ref (e.g., "@e3", "@c1") or a CSS selector.
   * Returns { locator } for refs or { selector } for CSS selectors.
   */
  async resolveRef(selector: string): Promise<{ locator: Locator } | { selector: string }> {
    if (selector.startsWith('@e') || selector.startsWith('@c')) {
      const ref = selector.slice(1); // "e3" or "c1"
      const entry = this.refMap.get(ref);
      if (!entry) {
        throw new Error(
          `Ref ${selector} not found. Run 'snapshot' to get fresh refs.`
        );
      }
      const count = await entry.locator.count();
      if (count === 0) {
        throw new Error(
          `Ref ${selector} (${entry.role} "${entry.name}") is stale — element no longer exists. ` +
          `Run 'snapshot' for fresh refs.`
        );
      }
      return { locator: entry.locator };
    }
    return { selector };
  }

  /** Get the ARIA role for a ref selector, or null for CSS selectors / unknown refs. */
  getRefRole(selector: string): string | null {
    if (selector.startsWith('@e') || selector.startsWith('@c')) {

View on GitHub (pinned to 94993f7401)

Solutions

  1. Run `snapshot` again and re-read the ref list before retrying the action.
  2. Switch to a CSS selector if you need a stable target that survives navigation.
  3. Confirm you are operating on the same tab that produced the snapshot.
  4. For scripted flows, call snapshot immediately before each action rather than caching refs across navigations.

Example fix

// before
await click(session, '@e5'); // refMap cleared by nav
// after
await snapshot(session); // refresh refs
await click(session, '@e5');
Defensive patterns

Strategy: retry

Validate before calling

function refExists(session: TabSession, selector: string): boolean {
  if (!selector.startsWith('@e') && !selector.startsWith('@c')) return true; // CSS — out of scope
  const ref = selector.slice(1);
  return session.getRefCount() > 0 && session.getRefEntries().some(e => e.ref === ref);
}

Type guard

function isKnownRef(session: TabSession, sel: string): boolean {
  if (!/^@[ec]/.test(sel)) return true;
  return session.getRefEntries().some(e => e.ref === sel.slice(1));
}

Try / catch

async function clickSafe(session: TabSession, sel: string) {
  try { return await click(session, sel); }
  catch (e: any) {
    if (/^Ref .* not found/.test(e.message)) {
      await snapshot(session); // refresh refs
      return click(session, sel);
    }
    throw e;
  }
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12). Data as JSON: /api/errors/c91ed11d42f4b3ac. Report an issue: GitHub.