{"record":{"id":"39554959c539258f","repo":"can1357/oh-my-pi","slug":"vibe-sessions-require-a-stable-parent-session-id","errorCode":null,"errorMessage":"Vibe sessions require a stable parent session id.","messagePattern":"Vibe sessions require a stable parent session id\\.","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/vibe/runtime.ts","lineNumber":364,"sourceCode":"\t\t\tsuspended: false,\n\t\t\tterminalPersisted: false,\n\t\t});\n\t}\n\n\treadonly #records = new Map<string, VibeRecord>();\n\treadonly #terminationTails = new Map<string, Promise<void>>();\n\treadonly #terminatedScopes = new Set<string>();\n\t#teardownGraceMs = VIBE_TEARDOWN_GRACE_MS;\n\n\t/** Override the teardown grace period for deterministic lifecycle tests. */\n\tsetTeardownGraceForTesting(timeoutMs: number): void {\n\t\tthis.#teardownGraceMs = Math.max(1, timeoutMs);\n\t}\n\n\townerScope(session: VibeParentSession): VibeOwnerScope {\n\t\tconst parentSessionId = session.getSessionId?.();\n\t\tif (!parentSessionId) {\n\t\t\tthrow new ToolError(\"Vibe sessions require a stable parent session id.\");\n\t\t}\n\t\tconst parentSessionFile = session.getSessionFile();\n\t\treturn {\n\t\t\townerId: session.getAgentId?.() ?? MAIN_AGENT_ID,\n\t\t\tparentSessionId,\n\t\t\tparentSessionFile: parentSessionFile ? path.resolve(parentSessionFile) : null,\n\t\t};\n\t}\n\n\t/** Re-open spawn admission after an explicit Vibe-mode entry. */\n\tactivateScope(scope: VibeOwnerScope): void {\n\t\tthis.#terminatedScopes.delete(scopeKey(scope, \"\"));\n\t}\n\n\tasync #withTerminationLock<T>(scope: VibeOwnerScope, operation: () => Promise<T>): Promise<T> {\n\t\tconst key = scopeKey(scope, \"\");\n\t\tconst predecessor = this.#terminationTails.get(key) ?? Promise.resolve();\n\t\tconst released = Promise.withResolvers<void>();","sourceCodeStart":346,"sourceCodeEnd":382,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/vibe/runtime.ts#L346-L382","documentation":"The Vibe runtime scopes every worker record to a parent session (ownerId + parentSessionId + parentSessionFile). ownerScope() computes that scope from the parent session object, and throws when session.getSessionId?.() returns nothing. Without a stable parent session id the registry could not key records, persist lifecycle tombstones, or verify that the parent session has not changed between operations.","triggerScenarios":"Calling ownerScope() (directly or via currentScope/listIds/scope, and transitively via spawn/kill/persistModeExit) with a VibeParentSession whose getSessionId() is undefined or returns an empty string — e.g. a stub session or a session constructed before its id was assigned.","commonSituations":"Embedding the SDK and passing a hand-rolled VibeParentSession that omits getSessionId; building a synthetic session in tests without an id; a session implementation whose id is set lazily and read before initialization.","solutions":["Ensure the VibeParentSession implementation's getSessionId() returns a non-empty stable id before invoking any vibe operation.","If constructing a session manually (SDK/test), assign a durable id (e.g. UUID persisted with the session file) at construction time.","Fix lazy initialization so getSessionId() is never consulted before the id is set, or make ownerScope callers wait for initialization."],"exampleFix":"// before\nconst registry = VibeSessionRegistry.global();\nregistry.scope(sessionWithoutId as VibeParentSession); // throws\n// after\nconst session = sessionWithoutId as VibeParentSession;\nif (!session.getSessionId?.()) session.initialize({ id: crypto.randomUUID() });\nregistry.scope(session);","handlingStrategy":"validation","validationCode":"const id = session.getSessionId?.();\nif (!id) throw new Error(\"Parent session must have a stable id before vibe operations\");","typeGuard":"function hasStableSessionId(s: VibeParentSession): s is VibeParentSession & { getSessionId: () => string } {\n  return typeof s.getSessionId === \"function\" && !!s.getSessionId();\n}","tryCatchPattern":"try {\n  registry.ownerScope(session);\n} catch (err) {\n  if (err instanceof ToolError && err.message.includes(\"stable parent session id\")) {\n    session.initialize({ id: crypto.randomUUID() });\n  } else throw err;\n}","preventionTips":["Assign a durable session id at session construction, never lazily","Type-check VibeParentSession implementations so getSessionId is non-optional","Unit-test custom session doubles with ownerScope before using them in vibe flows"],"tags":["session","configuration","sdk"],"backgroundTag":"missing-session-id","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}