{"record":{"id":"a47d8c37450175c7","repo":"can1357/oh-my-pi","slug":"a-prepaint-composer-is-already-active","errorCode":null,"errorMessage":"A prepaint composer is already active","messagePattern":"A prepaint composer is already active","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/modes/startup-composer.ts","lineNumber":74,"sourceCode":"\t/** Transfer terminal ownership exactly once. */\n\tadopt(): void {\n\t\tif (this.#adopted) return;\n\t\t// Safety net: startup paths that never applied resolved settings must\n\t\t// still hand InteractiveMode a raw-input terminal.\n\t\tthis.composer.enableInput();\n\t\tthis.composer.transfer();\n\t\tthis.#adopted = true;\n\t}\n\n\t/** Stop an unadopted composer when startup exits before InteractiveMode. */\n\tdispose(): void {\n\t\tif (!this.#adopted) this.composer.stop();\n\t}\n}\n\n/** Start the canonical Composer with speculative cached state, then refresh recent sessions. */\nexport function beginStartupComposer(options: PrepaintComposerOptions = {}): void {\n\tif (pendingComposer) throw new Error(\"A prepaint composer is already active\");\n\tconst cwd = options.cwd ?? process.cwd();\n\tconst useCache = options.cache !== false;\n\tconst cached = useCache\n\t\t? readComposerStartupCache(cwd)\n\t\t: {\n\t\t\t\tpreferences: undefined,\n\t\t\t\ttheme: undefined,\n\t\t\t\twelcome: undefined,\n\t\t\t\trecentSessions: [],\n\t\t\t\tlspServers: [],\n\t\t\t};\n\tconst theme = { ...cached.theme, ...options.theme };\n\tinitThemeSync(theme.symbolPreset, theme.colorBlindMode, theme.darkTheme, theme.lightTheme);\n\tconst preferences = { ...COMPOSER_DEFAULTS, ...cached.preferences, ...options.preferences };\n\tconst welcome: ComposerWelcomeUpdate = {\n\t\tversion: options.version ?? \"\",\n\t\tmodelName: cached.welcome?.modelName,\n\t\tproviderName: cached.welcome?.providerName,","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/modes/startup-composer.ts#L56-L92","documentation":"beginStartupComposer creates the singleton 'prepaint' composer used to speculatively render startup state. Because only one composer instance may own the terminal at a time, the module-level pendingComposer guard throws 'A prepaint composer is already active' if one is still live. Callers must stop/finish the existing composer before beginning another.","triggerScenarios":"Calling beginStartupComposer twice without the first composer being stopped/adopted — e.g. runCli and coldLaunch both invoking it on overlapping code paths, re-entrant startup, or a retry path that re-enters startup after a partial initialization.","commonSituations":"Double CLI initialization (embedding the CLI and also calling its startup path); error during startup that leaves the composer active, followed by a retry that calls beginStartupComposer again; tests reusing a module instance across startup attempts.","solutions":["Ensure the previous composer finished: call its stop() (or let adoption complete) before calling beginStartupComposer again.","Audit call sites (runCli, coldLaunch) so only one startup path runs per process; guard with a lifecycle flag.","Wrap in try/catch that stops the pending composer and retries once if 'A prepaint composer is already active' is thrown."],"exampleFix":"// before\nbeginStartupComposer({ cwd });\nbeginStartupComposer({ cwd }); // throws\n// after\nif (firstComposer) firstComposer.stop();\nbeginStartupComposer({ cwd });","handlingStrategy":"try-catch","validationCode":"// guard call sites with a module-level lifecycle flag\nif (startupComposerActive) throw new Error(\"startup already in progress\");","typeGuard":null,"tryCatchPattern":"try {\n  beginStartupComposer(options);\n} catch (err) {\n  if (err instanceof Error && err.message === \"A prepaint composer is already active\") {\n    // stop the pending composer and retry once\n  } else throw err;\n}","preventionTips":["Ensure exactly one startup entry point (runCli vs coldLaunch) executes per process.","Always stop or adopt the composer before any retry/re-entry into startup.","On startup failure, clean up the active composer in a finally block before retrying."],"tags":["lifecycle","singleton","startup","tui"],"backgroundTag":"singleton-already-initialized","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}