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

  1. Ensure the parent ToolSession is created with a real persisted SessionManager (a session file) before spawning vibe workers, so kill paths can persist tombstones.
  2. Upgrade/replace the SessionManager implementation so it provides recoverPersistenceFromCurrentState (it is an optional method the registry feature-detects).
  3. 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.
  4. 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

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


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