can1357/oh-my-pi · error · Error
Agent " " is already owned by another session generation.
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.
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…
- Agent " " was replaced during session initialization.
- Cannot start while is already collecting prompt lifecycle…
- experiment has running arms ( ); cancel them first
- run 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.
Appendix: 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)