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
- Perform vibe entry and exit on the same parent session object with an unchanged id, file, and agent id.
- If the session was forked, exit vibe mode before forking or re-enter vibe mode in the new session.
- 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
- Never fork/compact or rename sessions while vibe workers are active
- Run vibe entry and exit in the same agent context (same ownerId)
- Capture and compare the scope before lifecycle operations
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
- Agent "${resolvedAgentId}" is already owned by another sessi
- Agent "${resolvedAgentId}" was replaced during session initi
- Vibe mode exit cannot persist worker tombstones without the
- experiment ${id} has running arms (${live.map(r => r.jobName
- run ${jobName} is running; cancel it first
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/9b5fe52c826b056b.
Report an issue: GitHub.