can1357/oh-my-pi · error · ToolError

Vibe sessions require a stable parent session id.

Error message

Vibe sessions require a stable parent session id.

What it means

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.

Source

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

			suspended: false,
			terminalPersisted: false,
		});
	}

	readonly #records = new Map<string, VibeRecord>();
	readonly #terminationTails = new Map<string, Promise<void>>();
	readonly #terminatedScopes = new Set<string>();
	#teardownGraceMs = VIBE_TEARDOWN_GRACE_MS;

	/** Override the teardown grace period for deterministic lifecycle tests. */
	setTeardownGraceForTesting(timeoutMs: number): void {
		this.#teardownGraceMs = Math.max(1, timeoutMs);
	}

	ownerScope(session: VibeParentSession): VibeOwnerScope {
		const parentSessionId = session.getSessionId?.();
		if (!parentSessionId) {
			throw new ToolError("Vibe sessions require a stable parent session id.");
		}
		const parentSessionFile = session.getSessionFile();
		return {
			ownerId: session.getAgentId?.() ?? MAIN_AGENT_ID,
			parentSessionId,
			parentSessionFile: parentSessionFile ? path.resolve(parentSessionFile) : null,
		};
	}

	/** Re-open spawn admission after an explicit Vibe-mode entry. */
	activateScope(scope: VibeOwnerScope): void {
		this.#terminatedScopes.delete(scopeKey(scope, ""));
	}

	async #withTerminationLock<T>(scope: VibeOwnerScope, operation: () => Promise<T>): Promise<T> {
		const key = scopeKey(scope, "");
		const predecessor = this.#terminationTails.get(key) ?? Promise.resolve();
		const released = Promise.withResolvers<void>();

View on GitHub (pinned to 9690622007)

Solutions

  1. Ensure the VibeParentSession implementation's getSessionId() returns a non-empty stable id before invoking any vibe operation.
  2. If constructing a session manually (SDK/test), assign a durable id (e.g. UUID persisted with the session file) at construction time.
  3. Fix lazy initialization so getSessionId() is never consulted before the id is set, or make ownerScope callers wait for initialization.

Example fix

// before
const registry = VibeSessionRegistry.global();
registry.scope(sessionWithoutId as VibeParentSession); // throws
// after
const session = sessionWithoutId as VibeParentSession;
if (!session.getSessionId?.()) session.initialize({ id: crypto.randomUUID() });
registry.scope(session);
Defensive patterns

Strategy: validation

Validate before calling

const id = session.getSessionId?.();
if (!id) throw new Error("Parent session must have a stable id before vibe operations");

Type guard

function hasStableSessionId(s: VibeParentSession): s is VibeParentSession & { getSessionId: () => string } {
  return typeof s.getSessionId === "function" && !!s.getSessionId();
}

Try / catch

try {
  registry.ownerScope(session);
} catch (err) {
  if (err instanceof ToolError && err.message.includes("stable parent session id")) {
    session.initialize({ id: crypto.randomUUID() });
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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