can1357/oh-my-pi · error · ToolError
Vibe tombstone recovery requires parent-session persistence.
Error message
Vibe tombstone recovery requires parent-session persistence.
What it means
During VibeSessionRegistry.#killRecord, when a record is already killed (record.killed) and its terminal tombstone still needs persisting, the registry calls session.sessionManager?.recoverPersistenceFromCurrentState to re-materialize persistence state. If the parent session has no sessionManager, or its SessionManager does not expose recoverPersistenceFromCurrentState (optional capability), the registry throws this ToolError because it cannot safely reconcile the tombstone. The error is captured into persistenceError, retried once via the recovery path, and rethrown if unrecoverable.
Source
Thrown at packages/coding-agent/src/vibe/runtime.ts:1201
record: VibeRecord,
manager: AsyncJobManager | undefined,
session: VibeParentSession,
reason: VibeTombstoneReason,
persistTerminal = true,
teardownDeadline?: number,
): Promise<VibeKillOutcome> {
const registered = this.#registeredAgent(record);
const settlingJobs = new Set<AsyncJob>();
if (record.turn && manager) {
const job = manager.getJob(record.turn.jobId);
if (job) settlingJobs.add(job);
}
let persistenceError: unknown;
if (persistTerminal && !record.terminalPersisted) {
try {
if (record.killed) {
const recover = session.sessionManager?.recoverPersistenceFromCurrentState;
if (!recover) throw new ToolError("Vibe tombstone recovery requires parent-session persistence.");
await recover.call(session.sessionManager);
}
if (!this.#hasInMemoryTombstone(session, record) && record.childSessionFile) {
if (!(await this.#appendTombstone(session, record, reason))) {
throw new ToolError(`Vibe session "${record.id}" changed parent scope before termination.`);
}
}
record.terminalPersisted = true;
} catch (error) {
persistenceError = error;
}
}
record.killed = true;
record.queue.length = 0;
let cancelledTurn = false;
if (record.turn && manager) {
const job = manager.getJob(record.turn.jobId);
if (job) settlingJobs.add(job);View on GitHub (pinned to 9690622007)
Solutions
- Ensure the parent ToolSession is created with a real persisted SessionManager (a session file) before spawning vibe workers, so kill paths can persist tombstones.
- Upgrade/replace the SessionManager implementation so it provides recoverPersistenceFromCurrentState (it is an optional method the registry feature-detects).
- Avoid killing the same record twice without letting the first kill complete; check the kill outcome and retry mode-exit so #persistModeExit can reconcile instead of the killed-record path.
- Catch the ToolError from kill and re-run the teardown after re-initializing the parent session manager.
Example fix
// before: parent session created without persistence
const session = await createSession({ persist: false });
await vibe.kill(id); // ToolError: Vibe tombstone recovery requires parent-session persistence.
// after: create the session with a session file / real SessionManager
const session = await createSession({ sessionFile: "/path/to/session.jsonl" });
await vibe.kill(id); Defensive patterns
Strategy: try-catch
Validate before calling
const sm = session.sessionManager;
if (!sm || typeof sm.recoverPersistenceFromCurrentState !== "function") {
throw new Error("parent session persistence unavailable; cannot kill vibe workers safely");
} Type guard
function hasTombstoneRecovery(sm: unknown): sm is { recoverPersistenceFromCurrentState: () => Promise<void> } {
return typeof sm === "object" && sm !== null &&
"recoverPersistenceFromCurrentState" in sm &&
typeof (sm as { recoverPersistenceFromCurrentState?: unknown }).recoverPersistenceFromCurrentState === "function";
} Try / catch
try {
await vibe.kill(workerId);
} catch (error) {
if (error instanceof ToolError && error.message.includes("tombstone recovery requires parent-session persistence")) {
logger.warn("vibe kill skipped durable tombstone; worker left dead in memory", { workerId });
} else throw error;
} Prevention
- Always create the parent session with a persisted session file when using vibe workers.
- Feature-check recoverPersistenceFromCurrentState before enabling vibe kill paths in embedded/SDK usage.
- Avoid double-kill; check the kill outcome and let mode-exit reconcile unpersisted tombstones.
- Keep SessionManager implementations up to date with the vibe lifecycle API surface.
When it happens
Trigger: Calling kill (or mode-exit teardown) on a Vibe record whose record.killed is already true and record.terminalPersisted is false, while session.sessionManager is undefined (headless/embedded parent without persistence) or is a SessionManager version/facade lacking the optional recoverPersistenceFromCurrentState method.
Common situations: Embedding the agent SDK with persistence disabled (no session file) and then killing a vibe worker twice or killing after a prior persistence failure; running an older/compat SessionManager shim that predates recoverPersistenceFromCurrentState; teardown races where the first kill attempt threw before marking terminalPersisted.
Related errors
- Persistent credential block store ${store} is unavailable af
- Cleanse session could not be persisted
- Failed to import ${sourceName} session: ${message}
- ACP session cannot be forked before it is persisted: ${sessi
- Smithery API key not found after login.
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/70de792d5b85cbfc.
Report an issue: GitHub.