can1357/oh-my-pi · error · ToolError
Vibe mode exit requires atomic parent-session persistence.
Error message
Vibe mode exit requires atomic parent-session persistence.
What it means
After path checks pass, #persistModeExit requires the parent session manager to expose appendEntriesAtomically so all tombstone entries are written as one atomic batch. If sessionManager.appendEntriesAtomically is undefined, partial tombstone writes could corrupt lifecycle state, so it throws. This guards against minimal/stub session managers lacking the atomic-append capability.
Source
Thrown at packages/coding-agent/src/vibe/runtime.ts:515
currentScope.ownerId !== scope.ownerId ||
currentScope.parentSessionId !== scope.parentSessionId ||
currentScope.parentSessionFile !== scope.parentSessionFile
) {
throw new ToolError("Vibe parent session changed before mode exit could be persisted.");
}
const parentSessionFile = currentScope.parentSessionFile;
const persistedPending = pending.filter(record => record.childSessionFile !== undefined);
for (const record of persistedPending) {
if (
!parentSessionFile ||
path.resolve(parentSessionFile.slice(0, -6), `${record.id}.jsonl`) !== record.childSessionFile
) {
throw new ToolError(`Vibe session "${record.id}" changed parent scope before termination.`);
}
}
const appendEntriesAtomically = sessionManager.appendEntriesAtomically;
if (!appendEntriesAtomically) {
throw new ToolError("Vibe mode exit requires atomic parent-session persistence.");
}
await appendEntriesAtomically.call(sessionManager, () => {
for (const record of persistedPending) {
sessionManager.appendCustomEntry(VIBE_LIFECYCLE_CUSTOM_TYPE, {
...this.#eventBase(record),
action: "tombstone",
reason: "mode-exit",
});
}
sessionManager.appendModeChange?.("none");
});
for (const record of pending) record.terminalPersisted = true;
}
#manager(session: ToolSession): AsyncJobManager {
const manager = session.asyncJobManager;
if (!manager) {
throw new ToolError("Vibe sessions require async execution (no background job manager is available).");View on GitHub (pinned to 9690622007)
Solutions
- Provide a session manager that implements appendEntriesAtomically (upgrade to a current SessionManager).
- Extend the custom session manager to batch appends atomically and expose it via appendEntriesAtomically.
- In tests, use the real SessionManager instead of a partial stub, or stub appendEntriesAtomically as pass-through.
Example fix
// before
const manager = { appendCustomEntry: fn } as SessionManager; // no atomic append
await vibe.exit(session); // throws
// after
const manager = new SessionManager(sessionFile); // implements appendEntriesAtomically
await vibe.exit(session); Defensive patterns
Strategy: validation
Validate before calling
const sm = session.sessionManager;
if (sm && typeof (sm as { appendEntriesAtomically?: unknown }).appendEntriesAtomically !== "function") {
throw new Error("SessionManager lacks appendEntriesAtomically; upgrade or stub it before vibe exit");
} Type guard
function hasAtomicAppend(sm: unknown): sm is { appendEntriesAtomically: (fn: () => void) => Promise<void>; appendCustomEntry: (t: string, e: unknown) => void } {
return !!sm && typeof (sm as { appendEntriesAtomically?: unknown }).appendEntriesAtomically === "function";
} Try / catch
try {
await vibe.exit(session);
} catch (err) {
if (err instanceof ToolError && err.message.includes("atomic parent-session persistence")) {
session.sessionManager = upgradeToAtomicSessionManager(session.sessionManager);
await vibe.exit(session);
} else throw err;
} Prevention
- Use the real SessionManager, not partial stubs, in embeddings and tests
- Keep custom session managers implementing the full persistence interface
- Pin package versions so session manager and vibe runtime stay compatible
When it happens
Trigger: vibe exit (→ #killAllLocked → #persistModeExit) with a sessionManager implementation that has appendCustomEntry but not appendEntriesAtomically — e.g. a custom SessionManager, an older/stub implementation, or a test double missing the method.
Common situations: SDK embeddings using a hand-rolled session manager; older session manager versions predating atomic append; test doubles stubbing only part of the interface.
Related errors
- Cleanse session could not be persisted
- Failed to persist ${sourceName} session
- Vibe mode exit cannot persist worker tombstones without the
- Vibe session "${record.id}" changed parent scope before term
- Vibe parent session changed before the worker could start.
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/4b61d9c04f6e71a2.
Report an issue: GitHub.