{"record":{"id":"b2d387cd9190c155","repo":"musistudio/claude-code-router","slug":"failed-to-open-claude-app-spawnerror","errorCode":null,"errorMessage":"Failed to open Claude App: ${spawnError}","messagePattern":"Failed to open Claude App: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/cli.ts","lineNumber":193,"sourceCode":"      const runtimeResult = applyProfileRuntimeConfig(launchConfig, profile, launchConfig.APIKEY);\n      if (!runtimeResult.ok) {\n        throw new Error(runtimeResult.message);\n      }\n    }\n    if (resolvedSurface === \"cli\" && process.env[prepareProfileOnlyEnv] === \"1\") {\n    return;\n  }\n  if (profile.agent === \"claude-code\" && resolvedSurface === \"app\") {\n      applyClaudeAppGatewayConfig(launchConfig);\n      applyClaudeAppGatewayConfig(launchConfig, {\n        backup: false,\n        dataDir: resolveClaudeAppProfileUserDataDir(configDir, profile),\n        refreshModelDiscoveryCache: true\n      });\n      const launch = await launchClaudeAppProfile(configDir, profile, launchConfig);\n      const spawnError = await waitForImmediateSpawnError(launch.child, 500);\n      if (spawnError) {\n        throw new Error(`Failed to open Claude App: ${spawnError}`);\n      }\n      process.stdout.write(`Opened Claude App with ${profile.name || profile.id}.\\n`);\n      return;\n    }\n    if (profile.agent === \"zcode\" && resolvedSurface === \"app\" && profileOptions.agentArgs.length === 0) {\n      const launch = launchZcodeAppProfile(configDir, profile, launchConfig);\n      const spawnError = await waitForImmediateSpawnError(launch.child, 500);\n      if (spawnError) {\n        throw new Error(`Failed to open ZCode App: ${spawnError}`);\n      }\n      process.stdout.write(`Opened ZCode App with ${profile.name || profile.id}.\\n`);\n      return;\n    }\n\n    const plan = buildProfileLaunchPlan(configDir, profile, resolvedSurface, profileOptions.agentArgs);\n\n    if (path.isAbsolute(plan.command) && !existsSync(plan.command)) {\n      throw new Error(`Profile launcher was not found: ${plan.command}. Open CCR once or re-save the profile.`);","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/cli/src/cli.ts#L175-L211","documentation":"Thrown by installUpdate when there is no downloaded update ready to install — status.canInstall is false, meaning the download step never completed (or was not run) on this staging path.","triggerScenarios":"Calling installUpdate() before downloadUpdate() has succeeded, after a failed download, or after the staged update was cleared/restarted.","commonSituations":"UI flow bug that skips or races the download step; user clicks 'install' while download is still in progress; app restarted between download and install so no staged update exists; a prior download failed silently.","solutions":["Ensure downloadUpdate() awaited successfully before enabling/calling installUpdate()","Subscribe to update status and only offer install when canInstall is true","If download previously failed, re-run downloadUpdate() and handle its error first","After app restart, re-check for updates and re-download before installing"],"exampleFix":"// before\nawait updates.installUpdate(); // no staged download\n// after\nawait updates.downloadUpdate();\nif ((await updates.getStatus()).canInstall) {\n  await updates.installUpdate();\n}","handlingStrategy":"validation","validationCode":"const status = await updateService.getStatus();\nif (!status.canInstall) {\n  await updateService.downloadUpdate();\n}\nawait updateService.installUpdate();","typeGuard":"const canInstallUpdate = (s: UpdateStatus): boolean =>\n  s.canInstall === true;","tryCatchPattern":"try {\n  await updateService.installUpdate();\n} catch (error) {\n  if (error instanceof Error && /No downloaded update/.test(error.message)) {\n    await updateService.downloadUpdate(); // then retry install\n  } else { throw error; }\n}","preventionTips":["Gate install UI on status.canInstall, not on download initiation","Await downloadUpdate before enabling the install action","Re-check status after app restart — staged updates don't persist across sessions"],"tags":["electron","auto-update","state-machine","install"],"backgroundTag":"update-not-staged","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}