can1357/oh-my-pi · error · ToolError
${label} cannot run: this handle was invalidated after ${sta
Error message
${label} cannot run: this handle was invalidated after ${state.invalidatedBy} timed out; run tab.observe() or tab.ariaSnapshot() to resolve a fresh handle What it means
Element handles tracked by the tab worker can be invalidated when an operation on them times out. runGuardedHandleAction checks `state.invalidatedBy` before running any action; if a previous operation timed out and invalidated the handle, every subsequent action on that stale handle throws this error, telling you to re-observe.
Source
Thrown at packages/coding-agent/src/tools/browser/tab-worker.ts:385
type: ElementHandle["type"];
invalidatedBy?: string;
}
/** Symbol-keyed original methods travel with each cached handle without enumerating or colliding. */
const RAW_HANDLE_METHODS = Symbol("browser.rawHandleMethods");
type HandleWithRawMethods = ActionableHandle & { [RAW_HANDLE_METHODS]?: RawHandleMethods };
async function runGuardedHandleAction<T>(
handle: ElementHandle,
state: RawHandleMethods,
label: string,
signal: AbortSignal,
action: () => Promise<T>,
invalidate?: () => Promise<void>,
): Promise<T> {
if (state.invalidatedBy) {
throw new ToolError(
`${label} cannot run: this handle was invalidated after ${state.invalidatedBy} timed out; ` +
"run tab.observe() or tab.ariaSnapshot() to resolve a fresh handle",
);
}
throwIfAborted(signal);
const pending = action();
try {
return await untilAborted(signal, () => pending);
} catch (error) {
if (!signal.aborted) throw error;
state.invalidatedBy = label;
void pending.catch(() => undefined);
await withTimeout(
Promise.all([handle.dispose().catch(() => undefined), invalidate?.().catch(() => undefined)]),
HANDLE_ACTION_INVALIDATION_TIMEOUT_MS,
`Timed out invalidating ${label}`,
).catch(() => undefined);
throw error;View on GitHub (pinned to 9690622007)
Solutions
- Run `tab.observe()` or `tab.ariaSnapshot()` to resolve a fresh handle, then retry the action with the new id.
- Retry promptly after observing — handles go stale when the DOM changes again.
- Avoid reusing handle ids across multiple steps; resolve, act, discard.
- If timeouts are the root cause, make the selector more specific or wait for the element to be actionable before acting.
Example fix
// before tab.click(handleId); // handleId invalidated by earlier timeout // after const obs = await tab.observe(); const fresh = obs.find(el => el.role === "button" && el.name === "Submit"); await tab.click(fresh.id);
Defensive patterns
Strategy: try-catch
Try / catch
try {
await tab.click(handleId);
} catch (err) {
if (err instanceof ToolError && err.message.includes("was invalidated")) {
const fresh = (await tab.observe()).find(el => el.name === targetName);
await tab.click(fresh.id);
} else throw err;
} Prevention
- Resolve a fresh handle immediately before each action; don't cache handle ids
- After any timeout, re-observe before retrying
- Avoid actions on pages known to re-render between observe and act
When it happens
Trigger: Holding a handle from tab.observe()/tab.id() across a slow page update, letting an action on it time out (setting `invalidatedBy`), then calling another action (click/fill) on the same handle id.
Common situations: Pages that re-render or navigate between observe and act, making handles stale; long hangs on a click that poison the handle for later calls; agent loops reusing old handle ids after a timeout.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Timed out clicking ${selector} (seen ${lastSeen} matches; la
- timed out: {command}
- AnthropicConnectionTimeoutError
- Kimi device flow timed out
- xAI device-code request failed: ${error instanceof Error ? e
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/79c3a4b8517d4212.
Report an issue: GitHub.