{"record":{"id":"42afb9d3b5eca710","repo":"Hmbown/CodeWhale","slug":"the-menu-bar-app-did-not-exit-update-cancelled","errorCode":null,"errorMessage":"The menu-bar app did not exit; update cancelled.","messagePattern":"The menu-bar app did not exit; update cancelled\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/app/updates.mjs","lineNumber":130,"sourceCode":"export async function restartWithUpdate(prepared,destination) {\n  const logDir=path.join(os.homedir(),\"Library\",\"Logs\",APP_NAME); fs.mkdirSync(logDir,{recursive:true});\n  const log=fs.openSync(path.join(logDir,\"update.log\"),\"a\",0o600);\n  const child=spawn(process.execPath,[fileURLToPath(import.meta.url),\"--apply\",prepared.bundle,destination,String(process.pid),String(process.ppid)],{detached:true,stdio:[\"ignore\",log,log]});\n  try { await new Promise((resolve,reject)=>{child.once(\"spawn\",resolve);child.once(\"error\",reject);}); child.unref(); }\n  finally { fs.closeSync(log); }\n}\n\nif(process.argv[1]===fileURLToPath(import.meta.url)&&process.argv[2]===\"--apply\") {\n  const [source,destination,owner,launcher]=process.argv.slice(3);\n  let result;\n  try {\n    verifyReleaseBundle(source);\n    process.kill(Number(owner),\"SIGTERM\");\n    for(let i=0;i<100;i++) { try { process.kill(Number(owner),0); } catch { break; } await new Promise(resolve=>setTimeout(resolve,100)); }\n    try { process.kill(Number(owner),0); throw new Error(\"The running helper did not stop; update cancelled.\"); } catch(error) { if(error.code!==\"ESRCH\") throw error; }\n    // LaunchServices must not send Reopen to the retiring menu-bar process.\n    for(let i=0;i<100;i++) { try { process.kill(Number(launcher),0); } catch { break; } await new Promise(resolve=>setTimeout(resolve,100)); }\n    try { process.kill(Number(launcher),0); throw new Error(\"The menu-bar app did not exit; update cancelled.\"); } catch(error) { if(error.code!==\"ESRCH\") throw error; }\n    const receipt=replaceMacBundle(source,destination,{verify:verifyReleaseBundle});\n    const installed=spawnSync(\"/usr/libexec/PlistBuddy\",[\"-c\",\"Print :CFBundleShortVersionString\",path.join(destination,\"Contents\",\"Info.plist\")],{encoding:\"utf8\"});\n    if(installed.status!==0) throw new Error(\"The installed version could not be read.\");\n    console.log(JSON.stringify({version:installed.stdout.trim(),...receipt,installedAt:new Date().toISOString()}));\n    result={ok:true,message:`Updated to ${installed.stdout.trim()}. Choose Allow new sessions when you are ready.`};\n  } catch(error) {\n    console.error(error.message); process.exitCode=1;\n    result={ok:false,message:`The update could not be completed: ${String(error.message).slice(0,600)} Computer sessions remain stopped.`};\n  } finally {\n    // The apply process outlives the menu app. Carry its result into the next\n    // launch so a failed install is visible without hunting for update.log.\n    try {\n      fs.mkdirSync(stateDir(),{recursive:true});\n      const temporary=`${updateResultPath()}.${process.pid}.tmp`;\n      fs.writeFileSync(temporary,JSON.stringify({...result,completedAt:new Date().toISOString()})+\"\\n\",{mode:0o600});\n      fs.renameSync(temporary,updateResultPath());\n    } catch(error) { console.error(`Could not save the update result: ${error.message}`); }\n    if(destination&&fs.existsSync(destination)) spawnSync(\"open\",[\"-g\",\"-a\",destination]);","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/app/updates.mjs#L112-L148","documentation":"After the helper exits, the install step waits (up to 10 seconds) for the menu-bar launcher process to exit, because LaunchServices could otherwise send a Reopen event to the retiring menu-bar process during the swap. If the launcher is still alive after the grace period, the install is cancelled; only ESRCH (process gone) is accepted.","triggerScenarios":"The menu-bar app ignored SIGTERM-style shutdown beyond 10 seconds; the launcher PID is stale or was reused by another long-lived process; the launcher is stuck in a modal system dialog or blocked syscall and never processes its exit; permission issues prevent signaling it.","commonSituations":"Updating while the menu-bar app is showing a panel or alert; a hung menu-bar process from a previous crashed session; macOS re-launching or holding the app due to login-item/Restore- windows behavior; PID reuse.","solutions":["Quit the menu-bar app explicitly (its menu Quit action or Activity Monitor) and retry the update.","Check Activity Monitor / logs for a hung menu-bar process and force-quit it before updating.","Disable 'reopen windows' / login-item restore for the app during the update to avoid relaunch interference.","Reboot if a stale or PID-reused launcher keeps appearing, then rerun the update."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"try { process.kill(launcherPid, 0); console.log('menu-bar app still running; quit it before updating'); } catch { /* launcher already exited */ }","typeGuard":null,"tryCatchPattern":"try { await restartWithUpdate(prepared, destination); } catch (e) { if (/menu-bar app did not exit/.test(e.message)) { /* quit/force-quit the menu-bar app, then retry */ } else throw e; }","preventionTips":["Quit the menu-bar app (close any open panels) before updating.","Disable login-item window restore to avoid relaunch interference.","Investigate hung menu-bar processes instead of retrying blindly."],"tags":["process","macos","menubar","update"],"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-23T08:17:48.524Z"}