can1357/oh-my-pi · error · Error
Agent "${resolvedAgentId}" is already owned by another sessi
Error message
Agent "${resolvedAgentId}" is already owned by another session generation. What it means
Agent identity is guarded by a global AgentLifecycleManager and an agent registry so only one live session generation can own a given agent id. When registerIfAvailable cannot claim the id (and no dead corpse could be reclaimed), construction aborts to prevent two sessions driving the same agent state.
Source
Thrown at packages/coding-agent/src/sdk.ts:3258
options.expectedAgentRef === undefined
? agentRegistry.register(registrationInput)
: agentRegistry.registerIfAvailable(registrationInput, options.expectedAgentRef);
if (!registeredAgentRef && options.expectedAgentRef === null) {
// A fresh spawn collided with an existing id. If that id is held by a
// provably-dead parked corpse — no live session, no reviver — reclaim it
// so this new generation can take the id instead of failing forever at
// construction. Without this, one such corpse (isolated-run park,
// interrupted construction) poisons the id for the whole process (#8490).
// The reclaim is gated by the lifecycle owner and only touches the
// registry it manages; the corpse's transcript stays at history://.
const stale = agentRegistry.get(resolvedAgentId);
const lifecycle = AgentLifecycleManager.global();
if (stale && lifecycle.manages(agentRegistry) && (await lifecycle.reclaimDeadCorpse(resolvedAgentId, stale))) {
registeredAgentRef = agentRegistry.registerIfAvailable(registrationInput, null);
}
}
if (!registeredAgentRef) {
throw new Error(`Agent "${resolvedAgentId}" is already owned by another session generation.`);
}
// A reused parked ref remains parked until the new AgentSession is fully
// constructed and attached. Startup failure therefore leaves it revivable.
hasRegistered = options.expectedAgentRef === undefined || options.expectedAgentRef === null;
// Partition the initial enabled set for the xd:// transport. Tool instances
// remain in the canonical map; only presentation names move between layers.
// Mounting requires both transport halves in the granted set (`read xd://`
// discovers, `write xd://<tool>` executes); explicit-list sessions granted
// `read` without `write` can use the device-only transport registered by
// createTools without surfacing it when no device needs it.
if (toolSession.xdev) {
const topLevelToolNames: string[] = [];
const mountedNames: string[] = [];
for (const name of initialToolNames) {
const tool = toolRegistry.get(name);
const explicitlyRequested = explicitlyRequestedToolNameSet?.has(name) === true;
if (tool && xdevReadAvailable && xdevWriteAvailable && !explicitlyRequested && isMountableUnderXdev(tool))View on GitHub (pinned to 9690622007)
Solutions
- Close the other session that owns this agent, or use a different agent id
- Ensure the previous process actually exited (kill orphaned omp processes) so the corpse can be reclaimed
- Retry after the owning session is disposed; use expectedAgentRef when resuming intentionally
Example fix
// before
await createAgentSession({ agentId: "my-agent" }); // second concurrent call
// after
// run sequentially, or pick a unique id per session
await createAgentSession({ agentId: "my-agent-2" }); Defensive patterns
Strategy: retry
Validate before calling
const lifecycle = AgentLifecycleManager.global();
if (lifecycle.manages(registry) && lifecycle.isOwnedByLiveSession(agentId)) {
throw new Error(`agent ${agentId} is already in use; pick another id or close the other session`);
} Type guard
null
Try / catch
try {
session = await createAgentSession({ agentId });
} catch (err) {
if (err instanceof Error && err.message.includes("already owned by another session generation")) {
await waitForAgentRelease(agentId); // poll until the other session disposes
session = await createAgentSession({ agentId });
} else throw err;
} Prevention
- Ensure only one process creates sessions for a given agent id (use a lock or supervisor)
- Dispose sessions deterministically so registry entries release promptly
- Use unique agent ids for concurrent sessions
When it happens
Trigger: createAgentSession for an agentId whose registry entry is held by another running session generation, with no stale/dead corpse to reclaim.
Common situations: Opening the same project/agent in two CLI instances; a previous session still alive holding the lock; orphaned process from a crash that the lifecycle manager has not classified as dead.
Related errors
- Vibe parent session changed before mode exit could be persis
- Agent "${resolvedAgentId}" was replaced during session initi
- Vibe mode exit cannot persist worker tombstones without the
- experiment ${id} has running arms (${live.map(r => r.jobName
- run ${jobName} is running; cancel it first
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/e17fb7895e217ee4.
Report an issue: GitHub.