{"record":{"id":"20e00b284e81bc8f","repo":"Hmbown/CodeWhale","slug":"the-running-helper-did-not-stop-update-cancelled","errorCode":null,"errorMessage":"The running helper did not stop; update cancelled.","messagePattern":"The running helper did not stop; update cancelled\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/app/updates.mjs","lineNumber":127,"sourceCode":"  } catch(error) { fs.rmSync(stage,{recursive:true,force:true}); throw error; }\n}\n\nexport 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});","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/app/updates.mjs#L109-L145","documentation":"The privileged install step (run via restartWithUpdate) sends SIGTERM to the running helper process (owner) and polls kill(pid, 0) up to 100 times (10 seconds). If the helper is still alive after that grace period, the install is cancelled with this error so it never replaces a bundle while its process is running. The error is only swallowed when kill reports ESRCH (process gone).","triggerScenarios":"The helper process ignored or was slow to handle SIGTERM beyond 10 seconds (long in-flight operation, blocked event loop, stuck in a syscall); the owner PID was reused by another process that refuses to die; permission problems making SIGTERM ineffective; the helper trapped SIGTERM and deliberately kept running.","commonSituations":"Updating while the computer-use helper is mid-session or hung; an elevated helper that the updater cannot signal; a system under heavy load delaying exit; PID reuse causing kill(0) to keep succeeding on an unrelated process.","solutions":["Stop any active computer sessions and retry the update so the helper can exit promptly on SIGTERM.","Check the helper's state (ps / logs under ~/Library/Logs/<APP_NAME>) for a hang or crash; kill it manually if stuck, then retry.","Ensure the updater has permission to signal the helper (same user/root as the process that launched it).","Reboot or manually terminate the stale helper if PID reuse is suspected, then rerun the update."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"try { process.kill(ownerPid, 0); console.log('helper still running; stop sessions before updating'); } catch { /* helper already stopped */ }","typeGuard":null,"tryCatchPattern":"try { await restartWithUpdate(prepared, destination); } catch (e) { if (/helper did not stop/.test(e.message)) { /* terminate or diagnose the hung helper, then retry */ } else throw e; }","preventionTips":["End all active computer sessions before starting an update.","Monitor helper health; restart a hung helper before updating.","Update at idle times so SIGTERM is handled within the 10s grace period."],"tags":["process","macos","update","sigterm"],"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"}