{"record":{"id":"70e08d5cc31cc752","repo":"can1357/oh-my-pi","slug":"vibe-parent-session-changed-before-the-worker-coul","errorCode":null,"errorMessage":"Vibe parent session changed before the worker could start.","messagePattern":"Vibe parent session changed before the worker could start\\.","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/vibe/runtime.ts","lineNumber":909,"sourceCode":"\t\t};\n\t\tconst key = scopeKey(scope, id);\n\t\tthis.#records.set(key, record);\n\t\tlet spawnPersisted = false;\n\t\ttry {\n\t\t\tif (childSessionFile) {\n\t\t\t\tspawnPersisted = await this.#appendLifecycleEvent(\n\t\t\t\t\tsession,\n\t\t\t\t\t{\n\t\t\t\t\t\t...this.#eventBase(record),\n\t\t\t\t\t\taction: \"spawn\",\n\t\t\t\t\t\tcli: args.cli,\n\t\t\t\t\t\tagent: agent.name,\n\t\t\t\t\t\tchildSessionFile: childSessionName,\n\t\t\t\t\t\tcreatedAt,\n\t\t\t\t\t},\n\t\t\t\t\trecord.parentSessionFile,\n\t\t\t\t);\n\t\t\t\tif (!spawnPersisted) throw new ToolError(\"Vibe parent session changed before the worker could start.\");\n\t\t\t}\n\t\t\tconst jobId = this.#registerTurnJob(session, manager, record, args.prompt, { first: true });\n\t\t\treturn { id, jobId };\n\t\t} catch (error) {\n\t\t\trecord.killed = true;\n\t\t\trecord.state = \"dead\";\n\t\t\trecord.lastActivityAt = Date.now();\n\t\t\trecord.lastActivity = \"spawn failed\";\n\t\t\tif (childSessionFile) {\n\t\t\t\t// A rejected terminal write leaves this dead record in the map so mode exit can retry it.\n\t\t\t\trecord.terminalPersisted = await this.#appendTombstone(session, record, \"spawn-failed\");\n\t\t\t\tif (!record.terminalPersisted) {\n\t\t\t\t\tthrow new ToolError(\"Vibe parent session changed before spawn failure could be persisted.\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.#records.delete(key);\n\t\t\tthrow error;\n\t\t}","sourceCodeStart":891,"sourceCodeEnd":927,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/vibe/runtime.ts#L891-L927","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Retry the spawn after the session settles (no concurrent fork/rename in flight).","Serialize spawn against session lifecycle operations (don't fork/compact while vibe spawns are pending).","Verify nothing mutates session file paths during the turn; perform vibe entry/spawn before any forking logic."],"exampleFix":"// before\nawait Promise.all([forkSession(session), vibe.spawn(session, opts)]); // racy\n// after\nawait vibe.spawn(session, opts);\nawait forkSession(session);","handlingStrategy":"retry","validationCode":"const expected = session.getSessionFile();\n// ensure no concurrent lifecycle op is in flight before spawning\nif (pendingLifecycleOps.has(session.getSessionId?.() ?? \"\")) {\n  throw new Error(\"Session lifecycle operation in flight; defer vibe spawn\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  await vibe.spawn(session, { cli, prompt });\n} catch (err) {\n  if (err instanceof ToolError && err.message.includes(\"changed before the worker could start\")) {\n    await Bun.sleep(250);\n    return vibe.spawn(session, { cli, prompt }); // parent file now settled\n  } else throw err;\n}","preventionTips":["Never fork/rename/compact the parent session concurrently with spawns","Serialize lifecycle mutations and vibe operations per session","Re-check session file stability after any async boundary before spawning"],"tags":["race-condition","session","persistence"],"backgroundTag":"session-identity-mismatch","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}