{"record":{"id":"8f5444ec0c7340a3","repo":"Hmbown/CodeWhale","slug":"no-agent-pointer-button-is-held-by-this-session","errorCode":null,"errorMessage":"no agent pointer button is held by this session","messagePattern":"no agent pointer button is held by this session","errorType":"exception","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/darwin.mjs","lineNumber":1055,"sourceCode":"      }\n      // A hover has to leave the pointer where it was asked to go.\n      const r = await gesture([{ type: MOUSE_MOVED, x: target.x, y: target.y, button: 0, clickState: 0 }], { restore: false, guard: target });\n      return { action_sent: true, strategy: \"event\", at: { x: target.x, y: target.y }, ...pointerCost(r) };\n    },\n    left_mouse_down: async ({ target } = {}) => {\n      assertInScreen(target?.x, target?.y);\n      requireSharedPointer();\n      if (state.pointerLease) throw new ExecError(\"this session already holds the left pointer button; release it first\");\n      await assertOwnsPoint(target.x, target.y);\n      state.pointerLease = await nativeLease(\"pointer_sequence\", { steps: [\n          { type: MOUSE_MOVED, x: target.x, y: target.y, button: 0, clickState: 0 },\n          { type: MOUSE.left.down, x: target.x, y: target.y, button: 0, clickState: 1 },\n        ], restore: false });\n      state.pointer = { x: target.x, y: target.y };\n      return { action_sent: true, strategy: \"event\", at: state.pointer, ...pointerCost(state.pointerLease.receipt) };\n    },\n    left_mouse_up: async ({ target } = {}) => {\n      if (!state.pointerLease) throw new ExecError(\"no agent pointer button is held by this session\");\n      const loc = target ?? state.pointer;\n      if (!loc) throw new ExecError(\"no agent pointer position — mouse_move or left_mouse_down first\");\n      assertInScreen(loc.x, loc.y);\n      // No ownership guard: the button is already held, and the drag may have\n      // legitimately left the originating window.\n      try { await withSignal(null, () => state.pointerLease.release({ point: loc })); }\n      finally { state.pointerLease = null; }\n      state.pointer = { x: loc.x, y: loc.y };\n      return { action_sent: true, strategy: \"event\", at: state.pointer, pointer_moved: true, pointer_restored: false };\n    },\n    left_click_drag: async ({ from_target: from, to } = {}) => {\n      assertInScreen(from?.x, from?.y); assertInScreen(to?.x, to?.y);\n      const steps = [\n        { type: MOUSE_MOVED, x: from.x, y: from.y, button: 0, clickState: 0 },\n        { type: MOUSE.left.down, x: from.x, y: from.y, button: 0, clickState: 1, delayMs: 60 },\n      ];\n      const n = 12;\n      for (let i = 1; i <= n; i++) {","sourceCodeStart":1037,"sourceCodeEnd":1073,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/backends/darwin.mjs#L1037-L1073","documentation":"left_mouse_up releases the pointer lease held by left_mouse_down. If state.pointerLease is null — no button was ever pressed, or it was already released — the call throws this error instead of sending a stray AX/CG mouse-up event that could pop up a context menu or complete an unintended click.","triggerScenarios":"Calling left_mouse_up without a prior left_mouse_down in this session, or calling it twice (the first call sets state.pointerLease = null in its finally block).","commonSituations":"Agent loop unconditionally emits mouse_up after every action; a previous mouse_up already fired on an error path but the retry logic repeats it; the session was recreated (new state object) and the lease from the old session is gone.","solutions":["Only call left_mouse_up after a successful left_mouse_down in the same session.","Track whether the button is held on the caller side and skip the mouse_up when it is not.","If the button was physically stuck from a prior session, use mouse_move first to re-establish pointer position, then rebuild the press/release pair rather than sending an orphan mouse_up."],"exampleFix":"// before: unconditional release\nawait left_mouse_down({ x: 50, y: 60 });\nawait left_mouse_up();\nawait left_mouse_up(); // second call throws\n\n// after: guard on caller side\nlet held = false;\nif (!held) return;\nawait left_mouse_up();\nheld = false;","handlingStrategy":"type-guard","validationCode":"let held = false; // set true after a successful left_mouse_down\nfunction canMouseUp() { return held; }","typeGuard":"function hasActivePointerLease(sessionState) {\n  return sessionState != null && sessionState.pointerLease != null;\n}","tryCatchPattern":"try {\n  if (!held) return; // nothing to release\n  await left_mouse_up();\n  held = false;\n} catch (e) {\n  if (!String(e.message).includes('no agent pointer button is held')) throw e;\n  held = false; // already released; reconcile local state\n}","preventionTips":["Track hold/release symmetrically in caller-side state and skip redundant mouse_up calls.","Never emit left_mouse_up in a generic cleanup path when no drag was started.","Remember a session rebuild resets the lease — do not carry held-button state across sessions without re-establishing it."],"tags":["pointer","mouse","state-conflict","macos"],"backgroundTag":"invalid-state-transition","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}