can1357/oh-my-pi · error · ToolError
Vibe session "${record.id}" changed parent scope before its
Error message
Vibe session "${record.id}" changed parent scope before its turn started. What it means
At the start of every vibe worker turn, #registerTurnJob appends a 'turn-started' lifecycle event to the parent session file via #appendLifecycleEvent, which validates the current parent scope against record.parentSessionFile. If the event cannot be persisted because the parent session was switched, branched, compacted, or its owner changed before the turn began, and the record has a childSessionFile, the turn aborts with this ToolError before spawning any work. The error is then wrapped into a VibeTurnError by the catch block.
Source
Thrown at packages/coding-agent/src/vibe/runtime.ts:1424
const jobId = manager.register(
"task",
`vibe ${record.cli} ${record.id}: ${firstLine(message, 60)}`,
async ({ jobId: ownJobId, signal }) => {
record.state = "running";
record.turnCount = turnIndex;
record.lastActivityAt = Date.now();
try {
const turnStartedPersisted = await this.#appendLifecycleEvent(
session,
{
...this.#eventBase(record),
action: "turn-started",
turn: turnIndex,
},
record.parentSessionFile,
);
if (record.childSessionFile && !turnStartedPersisted) {
throw new ToolError(`Vibe session "${record.id}" changed parent scope before its turn started.`);
}
const result = options.first
? await runSubprocess(await this.#buildSpawnOptions(session, record, message, signal, onProgress))
: await runSubagentFollowUpTurn({
id: record.id,
agent: record.agent,
message,
description: `vibe ${record.cli} session`,
signal,
onProgress,
eventBus: session.eventBus,
subagentEventBus: session.subagentEventBus,
artifactsDir: session.getSessionFile()?.slice(0, -6),
});
return await this.#settleTurn(session, manager, record, turn, ownJobId, turnIndex, result);
} catch (error) {
if (error instanceof VibeTurnError) throw error;
await this.#finishTurn(session, manager, record, ownJobId);View on GitHub (pinned to 9690622007)
Solutions
- Drain or kill vibe workers before switching/compacting the parent session; deliver queued turns in the original scope.
- Re-register the vibe worker under the current session scope (new record bound to the current parentSessionFile) and send the message there.
- Switch back to the original parent session file before sending the follow-up turn.
- If the turn is not essential, treat the wrapped VibeTurnError as terminal for that turn and start a fresh worker.
Example fix
// before: follow-up after session switch await session.switchTo(newFile); await vibe.send(workerId, "continue"); // turn fails: changed parent scope before its turn started // after: send while scope matches, or respawn under the new scope await vibe.send(workerId, "continue"); await session.switchTo(newFile);
Defensive patterns
Strategy: validation
Validate before calling
const current = session.getSessionFile();
const canRunTurn = !!current && record.parentSessionFile !== null &&
path.resolve(current) === path.resolve(record.parentSessionFile) &&
session.getSessionId?.() === record.parentSessionId;
if (!canRunTurn) throw new Error("parent scope changed; re-register worker before sending turns"); Type guard
null
Try / catch
try {
await vibe.send(workerId, message);
} catch (error) {
if (error instanceof VibeTurnError && error.message.includes("changed parent scope before its turn started")) {
// restore scope or respawn the worker under the current session
} else throw error;
} Prevention
- Deliver all queued turns before switching or compacting the parent session.
- After an intentional scope change, kill the old record and register a fresh worker bound to the new session file.
- Gate automated follow-up sends on a scope check against record.parentSessionFile.
- Keep worker lifetimes shorter than the parent session's lifetime.
When it happens
Trigger: Sending a message/new turn to a vibe worker after the parent session scope changed: getSessionFile() resolves to a different path than record.parentSessionFile, or session id / owner agent id no longer match, so #appendLifecycleEvent returns false while record.childSessionFile is set.
Common situations: Queueing follow-up prompts to a background vibe worker and then switching sessions in the same process; session compaction replaced the session file between turns; the worker's owning agent changed; automation that switches sessions while vibe workers still have queued turns.
Related errors
- Vibe session "${record.id}" no longer resolves to this paren
- [vibe:${record.id} cli=${record.cli} turn=${turnIndex}] turn
- DAP adapter ${this.adapter.name} is not running
- Debug session ${root.id} is still active. Terminate it befor
- Cannot ${action} on a disposed JS runtime
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/8492d9a05d47da91.
Report an issue: GitHub.