can1357/oh-my-pi · error · ToolError

Vibe parent session changed before the worker could start.

Error message

Vibe parent session changed before the worker could start.

What it means

When spawning a worker with a parent session file, the runtime persists the worker's birth record to the parent session and verifies the parent file is still the expected one. If the guarded append reports the parent session file no longer matches (#appendLifecycleEvent returned false), the spawn is aborted with this error, and the just-created record is marked killed/dead in the catch block. It prevents workers from being parented to a session that changed underneath the spawn.

Source

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

		};
		const key = scopeKey(scope, id);
		this.#records.set(key, record);
		let spawnPersisted = false;
		try {
			if (childSessionFile) {
				spawnPersisted = await this.#appendLifecycleEvent(
					session,
					{
						...this.#eventBase(record),
						action: "spawn",
						cli: args.cli,
						agent: agent.name,
						childSessionFile: childSessionName,
						createdAt,
					},
					record.parentSessionFile,
				);
				if (!spawnPersisted) throw new ToolError("Vibe parent session changed before the worker could start.");
			}
			const jobId = this.#registerTurnJob(session, manager, record, args.prompt, { first: true });
			return { id, jobId };
		} catch (error) {
			record.killed = true;
			record.state = "dead";
			record.lastActivityAt = Date.now();
			record.lastActivity = "spawn failed";
			if (childSessionFile) {
				// A rejected terminal write leaves this dead record in the map so mode exit can retry it.
				record.terminalPersisted = await this.#appendTombstone(session, record, "spawn-failed");
				if (!record.terminalPersisted) {
					throw new ToolError("Vibe parent session changed before spawn failure could be persisted.");
				}
			}
			this.#records.delete(key);
			throw error;
		}

View on GitHub (pinned to 9690622007)

Solutions

  1. Retry the spawn after the session settles (no concurrent fork/rename in flight).
  2. Serialize spawn against session lifecycle operations (don't fork/compact while vibe spawns are pending).
  3. Verify nothing mutates session file paths during the turn; perform vibe entry/spawn before any forking logic.

Example fix

// before
await Promise.all([forkSession(session), vibe.spawn(session, opts)]); // racy
// after
await vibe.spawn(session, opts);
await forkSession(session);
Defensive patterns

Strategy: retry

Validate before calling

const expected = session.getSessionFile();
// ensure no concurrent lifecycle op is in flight before spawning
if (pendingLifecycleOps.has(session.getSessionId?.() ?? "")) {
  throw new Error("Session lifecycle operation in flight; defer vibe spawn");
}

Try / catch

try {
  await vibe.spawn(session, { cli, prompt });
} catch (err) {
  if (err instanceof ToolError && err.message.includes("changed before the worker could start")) {
    await Bun.sleep(250);
    return vibe.spawn(session, { cli, prompt }); // parent file now settled
  } else throw err;
}

Prevention

When it happens

Trigger: Calling vibe_spawn when the parent session's file changes (fork, rename, resume-from-copy, concurrent compaction) between scope capture and the spawn-persist step, so record.parentSessionFile no longer matches the session's current file.

Common situations: Session forking/compaction racing with worker spawn; the agent framework switching session files mid-turn; concurrent tool calls where one exits/re-parents the session while another spawns.

Related errors


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