can1357/oh-my-pi · error
A prepaint composer is already active
Error message
A prepaint composer is already active
What it means
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.
Source
Thrown at packages/coding-agent/src/modes/startup-composer.ts:74
/** Transfer terminal ownership exactly once. */
adopt(): void {
if (this.#adopted) return;
// Safety net: startup paths that never applied resolved settings must
// still hand InteractiveMode a raw-input terminal.
this.composer.enableInput();
this.composer.transfer();
this.#adopted = true;
}
/** Stop an unadopted composer when startup exits before InteractiveMode. */
dispose(): void {
if (!this.#adopted) this.composer.stop();
}
}
/** Start the canonical Composer with speculative cached state, then refresh recent sessions. */
export function beginStartupComposer(options: PrepaintComposerOptions = {}): void {
if (pendingComposer) throw new Error("A prepaint composer is already active");
const cwd = options.cwd ?? process.cwd();
const useCache = options.cache !== false;
const cached = useCache
? readComposerStartupCache(cwd)
: {
preferences: undefined,
theme: undefined,
welcome: undefined,
recentSessions: [],
lspServers: [],
};
const theme = { ...cached.theme, ...options.theme };
initThemeSync(theme.symbolPreset, theme.colorBlindMode, theme.darkTheme, theme.lightTheme);
const preferences = { ...COMPOSER_DEFAULTS, ...cached.preferences, ...options.preferences };
const welcome: ComposerWelcomeUpdate = {
version: options.version ?? "",
modelName: cached.welcome?.modelName,
providerName: cached.welcome?.providerName,View on GitHub (pinned to 9690622007)
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.
Example fix
// before
beginStartupComposer({ cwd });
beginStartupComposer({ cwd }); // throws
// after
if (firstComposer) firstComposer.stop();
beginStartupComposer({ cwd }); Defensive patterns
Strategy: try-catch
Validate before calling
// guard call sites with a module-level lifecycle flag
if (startupComposerActive) throw new Error("startup already in progress"); Try / catch
try {
beginStartupComposer(options);
} catch (err) {
if (err instanceof Error && err.message === "A prepaint composer is already active") {
// stop the pending composer and retry once
} else throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Composer is not available for transfer
- Unable to read OMP_AUTH_BROKER_ACCOUNT_POOL_FILE at ${filePa
- blob broker exposure failed to start
- Unexpected response ${result.op}
- Cannot change working directory to ${parsed.cwd}: ${reason}.
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/a47d8c37450175c7.
Report an issue: GitHub.