{"record":{"id":"e82755e77e3896f9","repo":"Hmbown/CodeWhale","slug":"the-practice-apply-button-changed-run-the-check-again","errorCode":null,"errorMessage":"The practice Apply button changed. Run the check again.","messagePattern":"The practice Apply button changed\\. Run the check again\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"crates/tui/plugins/computer-use/app/background-check.mjs","lineNumber":71,"sourceCode":"        try { await call(\"screenshot\", { app_ref: { pid: child.pid }, path: path.join(demoDirectory, \"01-ready.png\") }); break; }\n        catch (error) {\n          if (controller.signal.aborted || !error.message.includes(\"not capturable\")) throw error;\n          await delay(100, undefined, { signal: controller.signal });\n        }\n      }\n    }\n    const entry = state.elements.find(element => element.role === \"AXTextField\" && element.label === \"Practice text\");\n    const apply = state.elements.find(element => element.role === \"AXButton\" && element.label === \"Apply\");\n    if (!entry || !apply) throw new Error(\"The practice controls could not be read. Check Accessibility permission and retry.\");\n    // Backend targets are resolved AX records, using the exact state index.\n    const focus = await call(\"resolve_element\", { app_ref: { pid: child.pid }, windowIndex: entry.windowIndex, path: entry.path });\n    if (!focus.found) throw new Error(\"The practice text field changed. Run the check again.\");\n    await call(\"left_click\", { target: { ...focus.element, type: \"element\", app_ref: { pid: child.pid } } });\n    const phrase = \"Background check complete 🐋\";\n    await call(\"type\", { text: phrase });\n    if (demoDirectory) await call(\"screenshot\", { app_ref: { pid: child.pid }, path: path.join(demoDirectory, \"02-entered.png\") });\n    const button = await call(\"resolve_element\", { app_ref: { pid: child.pid }, windowIndex: apply.windowIndex, path: apply.path });\n    if (!button.found) throw new Error(\"The practice Apply button changed. Run the check again.\");\n    await call(\"left_click\", { target: { ...button.element, type: \"element\", app_ref: { pid: child.pid } } });\n    await until(() => applied);\n    if (applied.value !== phrase) throw new Error(\"The text received by the practice window did not match. Check the app log and retry.\");\n    const capture = await call(\"screenshot\", { app_ref: { pid: child.pid }, path: path.join(scratch, \"practice.png\") });\n    if (demoDirectory) fs.copyFileSync(capture.file, path.join(demoDirectory, \"03-verified.png\"));\n    if (capture.app_ref?.pid !== child.pid || !capture.pixels?.w || !capture.pixels?.h) throw new Error(\"The practice window screenshot could not be verified. Check Screen Recording permission.\");\n    const count = latest.samples;\n    await until(() => latest.samples > count);\n    const isolated = latest.samples > 0 && latest.pointerChanges === 0 && latest.foregroundChanges === 0;\n    return { ok: isolated, edited: true, screenshot: true, samples: latest.samples, pointerChanges: latest.pointerChanges, foregroundChanges: latest.foregroundChanges,\n      message: isolated ? \"Text entered, Apply verified and the practice window captured. Your foreground app and pointer stayed unchanged.\"\n        : \"Edit and screenshot verified. Your app or pointer moved during the check, so background isolation is inconclusive.\" };\n  } finally {\n    clearTimeout(timer);\n    try { await closeSession(sessionId); }\n    finally {\n      if (child.exitCode === null && child.signalCode === null) child.kill(\"SIGTERM\");\n      fs.rmSync(scratch, { recursive: true, force: true });","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/app/background-check.mjs#L53-L89","documentation":"Identical guard to the text-field one, but for the 'Apply' button: after typing, the check re-resolves the button's AX record and throws if the live UI no longer contains it. It indicates the snapshot of the Apply button went stale between get_app_state and the second resolve_element call.","triggerScenarios":"In runBackgroundCheck, call('resolve_element', { app_ref: { pid: child.pid }, windowIndex: apply.windowIndex, path: apply.path }) returns { found: false } after typing the phrase.","commonSituations":"The practice window's layout re-indexed after text entry (button moved), a slow UI that rebuilt the view during typing, or window reordering between steps.","solutions":["Re-run the background check to take fresh AX snapshots.","Reduce desktop churn (no window/focus changes) while the check runs.","If deterministic, verify the practice app's Apply button still exists post-typing and report an app bug if it disappears."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await runBackgroundCheck({ bundle });\n} catch (e) {\n  if (String(e.message).includes(\"Apply button changed\")) {\n    return runBackgroundCheck({ bundle }); // stale AX snapshot — one retry\n  }\n  throw e;\n}","preventionTips":["Avoid UI churn (window moves, refocus) during the check so AX paths stay valid.","Retry once on stale-element errors; snapshots go stale transiently.","File an app bug if the Apply button deterministically disappears after typing."],"tags":["accessibility","stale-reference","race","computer-use"],"backgroundTag":"resource-not-found","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}