{"record":{"id":"bfa3761df3af54b7","repo":"can1357/oh-my-pi","slug":"vibe-mode-exit-cannot-persist-worker-tombstones-wi","errorCode":null,"errorMessage":"Vibe mode exit cannot persist worker tombstones without the parent session manager.","messagePattern":"Vibe mode exit cannot persist worker tombstones without the parent session manager\\.","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/vibe/runtime.ts","lineNumber":490,"sourceCode":"\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (event.action === \"tombstone\") terminalReason = event.reason;\n\t\t\telse if (event.action === \"tombstone-revoked\" && terminalReason === \"mode-exit\") terminalReason = undefined;\n\t\t}\n\t\treturn terminalReason !== undefined;\n\t}\n\n\tasync #persistModeExit(\n\t\tsession: VibeParentSession,\n\t\tscope: VibeOwnerScope,\n\t\trecords: readonly VibeRecord[],\n\t): Promise<void> {\n\t\tconst pending = records.filter(record => !record.terminalPersisted);\n\t\tconst sessionManager = session.sessionManager;\n\t\tif (!sessionManager) {\n\t\t\tif (pending.some(record => record.childSessionFile)) {\n\t\t\t\tthrow new ToolError(\"Vibe mode exit cannot persist worker tombstones without the parent session manager.\");\n\t\t\t}\n\t\t\tfor (const record of pending) record.terminalPersisted = true;\n\t\t\treturn;\n\t\t}\n\t\tconst currentScope = this.ownerScope(session);\n\t\tif (\n\t\t\tcurrentScope.ownerId !== scope.ownerId ||\n\t\t\tcurrentScope.parentSessionId !== scope.parentSessionId ||\n\t\t\tcurrentScope.parentSessionFile !== scope.parentSessionFile\n\t\t) {\n\t\t\tthrow new ToolError(\"Vibe parent session changed before mode exit could be persisted.\");\n\t\t}\n\t\tconst parentSessionFile = currentScope.parentSessionFile;\n\t\tconst persistedPending = pending.filter(record => record.childSessionFile !== undefined);\n\t\tfor (const record of persistedPending) {\n\t\t\tif (\n\t\t\t\t!parentSessionFile ||\n\t\t\t\tpath.resolve(parentSessionFile.slice(0, -6), `${record.id}.jsonl`) !== record.childSessionFile","sourceCodeStart":472,"sourceCodeEnd":508,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/vibe/runtime.ts#L472-L508","documentation":"When vibe mode exits, worker tombstones must be appended to the parent session so records survive restarts. #persistModeExit throws if any pending record has a childSessionFile but session.sessionManager is null — persistence is impossible. Records without a child session file are silently marked terminal-persisted instead.","triggerScenarios":"Calling vibe exit/kill-all (→ #killAllLocked → #persistModeExit) on a parent session whose sessionManager is unset, while at least one worker record still carries a childSessionFile that needs a tombstone appended.","commonSituations":"SDK embeddings that build a VibeParentSession without wiring a session manager; ephemeral/in-memory sessions used in tests; sessions opened in a mode that disables persistence (e.g. no session file configured).","solutions":["Attach a real session manager to the parent session (session.sessionManager) before exiting vibe mode.","If persistence is intentionally impossible, exit vibe mode before any worker with a child session file exists, or clear the childSessionFile association.","For ephemeral use, ensure records have no childSessionFile so the runtime can mark them terminal without persistence."],"exampleFix":"// before\nconst session = createStubSession(); // sessionManager undefined\nawait vibe.exit(session); // throws when workers exist\n// after\nconst session = createStubSession({ sessionManager: new SessionManager(sessionFile) });\nawait vibe.exit(session);","handlingStrategy":"validation","validationCode":"if (!session.sessionManager) {\n  throw new Error(\"Refusing vibe exit: session manager required to persist worker tombstones\");\n}","typeGuard":"function canPersistTombstones(s: VibeParentSession): boolean {\n  return !!s.sessionManager;\n}","tryCatchPattern":"try {\n  await vibe.exit(session);\n} catch (err) {\n  if (err instanceof ToolError && err.message.includes(\"without the parent session manager\")) {\n    logger.warn(\"vibe exit skipped tombstone persistence; attach a session manager\", { sessionId: session.getSessionId?.() });\n  } else throw err;\n}","preventionTips":["Always construct embedded sessions with a real SessionManager","Ensure workers are spawned only in persistent sessions when tombstones matter","Add an integration test exiting vibe mode with live workers"],"tags":["session","persistence","lifecycle"],"backgroundTag":"missing-session-manager","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}