can1357/oh-my-pi · error · ToolError

Vibe session "${record.id}" no longer resolves to this paren

Error message

Vibe session "${record.id}" no longer resolves to this parent session.

What it means

send() checks that a session id which exists in the global AgentRegistry still resolves to an agent owned by this parent session. If the id is registered globally but this registry's record has no corresponding registered agent, the session was taken over by, or re-registered under, a different owner — so this parent session may no longer message it. Thrown to prevent cross-session interference.

Source

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

		}
	}

	/**
	 * Send a message to a worker. Mid-turn and streaming → steering; mid-turn
	 * otherwise → queued for the next turn; idle/parked → starts a new
	 * background turn immediately.
	 */
	async send(session: ToolSession, args: { session: string; message: string }): Promise<VibeSendOutcome> {
		const scope = this.ownerScope(session);
		const record = this.#record(scope, args.session);
		if (record.state === "dead") {
			throw new ToolError(`Vibe session "${record.id}" is dead. Spawn a new one with vibe_spawn.`);
		}
		const message = args.message.trim();
		if (!message) throw new ToolError("Message must not be empty.");
		const registered = this.#registeredAgent(record);
		if (AgentRegistry.global().get(record.id) && !registered) {
			throw new ToolError(`Vibe session "${record.id}" no longer resolves to this parent session.`);
		}

		if (record.turn) {
			const live = registered?.session;
			if (live?.isStreaming) {
				await live.steer(message);
				record.lastActivityAt = Date.now();
				return { id: record.id, mode: "steered" };
			}
			record.queue.push(message);
			record.lastActivityAt = Date.now();
			return { id: record.id, mode: "queued" };
		}

		if (!registered || (registered.status !== "idle" && registered.status !== "parked")) {
			throw new ToolError(`Vibe session "${record.id}" no longer resolves to this parent session.`);
		}

View on GitHub (pinned to 9690622007)

Solutions

  1. Run vibe_status / list sessions from the current parent session to see which sessions it actually owns.
  2. Spawn a new worker with vibe_spawn instead of messaging the foreign-registered id.
  3. Avoid running multiple parent sessions that share worker names/ids concurrently.
  4. If this is a stale record from restored state, exit Vibe mode to clean up dead/orphaned records.

Example fix

// before
await registry.send(session, { session: adoptedId, message: 'status?' });
// ToolError: no longer resolves to this parent session

// after
const owned = await registry.spawn(session, { cli: 'codex', prompt: 'take over the task' });
await registry.send(session, { session: owned.id, message: 'status?' });
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the session is owned by this parent before sending
const owned = await registry.status(session, { sessions: [id] });
if (!owned.length) throw new Error(`session ${id} is not owned by this parent session`);

Try / catch

try {
	await registry.send(session, { session: id, message });
} catch (err) {
	if (err instanceof ToolError && err.message.includes('no longer resolves')) {
		// ownership lost: respawn a replacement worker
		return registry.spawn(session, { cli, prompt: message });
	}
	throw err;
}

Prevention

When it happens

Trigger: Calling send() for a session id that is present in AgentRegistry.global() but #registeredAgent(record) returns undefined — i.e. the worker agent was re-registered/adopted elsewhere or the record's registration was lost while the global entry persists.

Common situations: Resuming a session from persisted state where another parent session re-registered the worker; duplicate ids across concurrent owner scopes; restoring a vibe record whose agent was claimed by a different run.

Related errors


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