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

  1. Provide a session manager that implements appendEntriesAtomically (upgrade to a current SessionManager).
  2. Extend the custom session manager to batch appends atomically and expose it via appendEntriesAtomically.
  3. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/4b61d9c04f6e71a2. Report an issue: GitHub.