can1357/oh-my-pi · error · ToolError

Vibe mode has exited; enter Vibe mode again before spawning

Error message

Vibe mode has exited; enter Vibe mode again before spawning a worker.

What it means

Once vibe mode has exited for a scope, the scope key (with empty id) is added to #terminatedScopes. #spawnLocked refuses to spawn into a terminated scope so that tombstoned workers cannot silently reappear. Re-entering vibe mode via the explicit entry path (activateScope) clears the flag and re-opens admission.

Source

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

			});
			restored++;
		}
		return restored;
	}

	/** Spawn a persistent worker session and start its first turn in the background. */
	async spawn(session: ToolSession, args: { cli: VibeCli; name?: string; prompt: string }): Promise<VibeSpawnOutcome> {
		const scope = this.ownerScope(session);
		return this.#withTerminationLock(scope, () => this.#spawnLocked(session, scope, args));
	}

	async #spawnLocked(
		session: ToolSession,
		scope: VibeOwnerScope,
		args: { cli: VibeCli; name?: string; prompt: string },
	): Promise<VibeSpawnOutcome> {
		if (this.#terminatedScopes.has(scopeKey(scope, ""))) {
			throw new ToolError("Vibe mode has exited; enter Vibe mode again before spawning a worker.");
		}
		const manager = this.#manager(session);
		const { agent, modelOverride, modelRole } = this.#resolveWorker(session, args.cli);
		if (!session.agentOutputManager) {
			session.agentOutputManager = new AgentOutputManager(session.getArtifactsDir ?? (() => null));
		}
		const reservedIds = this.#persistedIds(session, scope);
		for (const ref of AgentRegistry.global().list()) reservedIds.add(ref.id);
		await session.agentOutputManager.reserve(reservedIds);
		const requestedName = args.name?.replace(/[^A-Za-z0-9_-]+/g, "").slice(0, 48);
		const id = await session.agentOutputManager.allocate(requestedName || generateTaskName());
		const parentSessionFile = scope.parentSessionFile;
		const childSessionName = `${id}.jsonl`;
		const childSessionFile = parentSessionFile
			? path.resolve(parentSessionFile.slice(0, -6), childSessionName)
			: undefined;
		const createdAt = Date.now();
		const record: VibeRecord = {

View on GitHub (pinned to 9690622007)

Solutions

  1. Enter vibe mode again (the explicit vibe-mode entry tool/command) before spawning; this calls activateScope and clears the terminated flag.
  2. Start a new vibe scope via the normal entry flow instead of reusing the exited one.
  3. Check for code paths that call exit prematurely while spawns are still expected.

Example fix

// before
await vibe.exit(session);
await vibe.spawn(session, { cli: "fast", prompt }); // throws
// after
await vibe.exit(session);
await vibe.enter(session); // re-opens the scope
await vibe.spawn(session, { cli: "fast", prompt });
Defensive patterns

Strategy: try-catch

Validate before calling

// no public predicate for terminated scopes; guard by re-entering after any exit
await vibe.enter(session); // activateScope clears the terminated flag
await vibe.spawn(session, { cli, prompt });

Try / catch

try {
  await vibe.spawn(session, { cli, prompt });
} catch (err) {
  if (err instanceof ToolError && err.message.includes("Vibe mode has exited")) {
    await vibe.enter(session);
    await vibe.spawn(session, { cli, prompt });
  } else throw err;
}

Prevention

When it happens

Trigger: Calling vibe_spawn (→ #spawnLocked) after a previous vibe exit on the same owner scope (same ownerId/parentSessionId/parentSessionFile) without an intervening explicit vibe-mode entry.

Common situations: An agent continues iterating and tries to spawn workers after the exit command; a resumed session replays a spawn after a persisted exit; retry logic re-issues a spawn that raced with the exit.

Related errors


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