can1357/oh-my-pi · error · ToolError

Vibe parent session changed before mode exit could be persis

Error message

Vibe parent session changed before mode exit could be persisted.

What it means

#persistModeExit re-computes the owner scope at exit time and compares it to the scope captured earlier (ownerId, parentSessionId, parentSessionFile). If any differ, the parent session identity changed between vibe-mode entry and exit, and tombstones must not be written to a session that may no longer be the owner. It throws to prevent cross-session corruption.

Source

Thrown at packages/coding-agent/src/vibe/runtime.ts:501

		scope: VibeOwnerScope,
		records: readonly VibeRecord[],
	): Promise<void> {
		const pending = records.filter(record => !record.terminalPersisted);
		const sessionManager = session.sessionManager;
		if (!sessionManager) {
			if (pending.some(record => record.childSessionFile)) {
				throw new ToolError("Vibe mode exit cannot persist worker tombstones without the parent session manager.");
			}
			for (const record of pending) record.terminalPersisted = true;
			return;
		}
		const currentScope = this.ownerScope(session);
		if (
			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, {

View on GitHub (pinned to 9690622007)

Solutions

  1. Perform vibe entry and exit on the same parent session object with an unchanged id, file, and agent id.
  2. If the session was forked, exit vibe mode before forking or re-enter vibe mode in the new session.
  3. Ensure agent id is stable across the tool call (don't run exit inside a subagent whose getAgentId differs from the owner).

Example fix

// before
const scope = registry.currentScope(session);
await forkSession(session); // changes sessionId/file
await registry.exit(session, scope); // throws
// after
await registry.exit(session, scope);
await forkSession(session);
Defensive patterns

Strategy: try-catch

Validate before calling

const scope = registry.currentScope(session);
const now = registry.ownerScope(session);
if (scope.ownerId !== now.ownerId || scope.parentSessionId !== now.parentSessionId || scope.parentSessionFile !== now.parentSessionFile) {
  throw new Error("Parent session identity changed; re-enter vibe mode before exiting");
}

Try / catch

try {
  await vibe.exit(session);
} catch (err) {
  if (err instanceof ToolError && err.message.includes("parent session changed before mode exit")) {
    // re-enter vibe mode in the new session scope, then exit there
    await vibe.enter(session);
    await vibe.exit(session);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling vibe exit (→ #killAllLocked → #persistModeExit) after the parent session's id, file path, or agent id changed — e.g. the session was forked/resumed into a new file, the agent id switched (subagent boundary), or a different session object was passed than the one that entered vibe mode.

Common situations: Session forking or compaction that replaces the session id/file mid-run; running the exit call from a different agent context than the spawn; resuming a session file from a new path between entry and exit.

Related errors


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