{"record":{"id":"e17fb7895e217ee4","repo":"can1357/oh-my-pi","slug":"agent-resolvedagentid-is-already-owned-by-ano","errorCode":null,"errorMessage":"Agent \"${resolvedAgentId}\" is already owned by another session generation.","messagePattern":"Agent \"(.+?)\" is already owned by another session generation\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/sdk.ts","lineNumber":3258,"sourceCode":"\t\t\toptions.expectedAgentRef === undefined\n\t\t\t\t? agentRegistry.register(registrationInput)\n\t\t\t\t: agentRegistry.registerIfAvailable(registrationInput, options.expectedAgentRef);\n\t\tif (!registeredAgentRef && options.expectedAgentRef === null) {\n\t\t\t// A fresh spawn collided with an existing id. If that id is held by a\n\t\t\t// provably-dead parked corpse — no live session, no reviver — reclaim it\n\t\t\t// so this new generation can take the id instead of failing forever at\n\t\t\t// construction. Without this, one such corpse (isolated-run park,\n\t\t\t// interrupted construction) poisons the id for the whole process (#8490).\n\t\t\t// The reclaim is gated by the lifecycle owner and only touches the\n\t\t\t// registry it manages; the corpse's transcript stays at history://.\n\t\t\tconst stale = agentRegistry.get(resolvedAgentId);\n\t\t\tconst lifecycle = AgentLifecycleManager.global();\n\t\t\tif (stale && lifecycle.manages(agentRegistry) && (await lifecycle.reclaimDeadCorpse(resolvedAgentId, stale))) {\n\t\t\t\tregisteredAgentRef = agentRegistry.registerIfAvailable(registrationInput, null);\n\t\t\t}\n\t\t}\n\t\tif (!registeredAgentRef) {\n\t\t\tthrow new Error(`Agent \"${resolvedAgentId}\" is already owned by another session generation.`);\n\t\t}\n\t\t// A reused parked ref remains parked until the new AgentSession is fully\n\t\t// constructed and attached. Startup failure therefore leaves it revivable.\n\t\thasRegistered = options.expectedAgentRef === undefined || options.expectedAgentRef === null;\n\n\t\t// Partition the initial enabled set for the xd:// transport. Tool instances\n\t\t// remain in the canonical map; only presentation names move between layers.\n\t\t// Mounting requires both transport halves in the granted set (`read xd://`\n\t\t// discovers, `write xd://<tool>` executes); explicit-list sessions granted\n\t\t// `read` without `write` can use the device-only transport registered by\n\t\t// createTools without surfacing it when no device needs it.\n\t\tif (toolSession.xdev) {\n\t\t\tconst topLevelToolNames: string[] = [];\n\t\t\tconst mountedNames: string[] = [];\n\t\t\tfor (const name of initialToolNames) {\n\t\t\t\tconst tool = toolRegistry.get(name);\n\t\t\t\tconst explicitlyRequested = explicitlyRequestedToolNameSet?.has(name) === true;\n\t\t\t\tif (tool && xdevReadAvailable && xdevWriteAvailable && !explicitlyRequested && isMountableUnderXdev(tool))","sourceCodeStart":3240,"sourceCodeEnd":3276,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/sdk.ts#L3240-L3276","documentation":"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.","triggerScenarios":"createAgentSession for an agentId whose registry entry is held by another running session generation, with no stale/dead corpse to reclaim.","commonSituations":"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.","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"],"exampleFix":"// before\nawait createAgentSession({ agentId: \"my-agent\" }); // second concurrent call\n// after\n// run sequentially, or pick a unique id per session\nawait createAgentSession({ agentId: \"my-agent-2\" });","handlingStrategy":"retry","validationCode":"const lifecycle = AgentLifecycleManager.global();\nif (lifecycle.manages(registry) && lifecycle.isOwnedByLiveSession(agentId)) {\n  throw new Error(`agent ${agentId} is already in use; pick another id or close the other session`);\n}","typeGuard":"null","tryCatchPattern":"try {\n  session = await createAgentSession({ agentId });\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"already owned by another session generation\")) {\n    await waitForAgentRelease(agentId); // poll until the other session disposes\n    session = await createAgentSession({ agentId });\n  } else throw err;\n}","preventionTips":["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"],"tags":["concurrency","session","lifecycle"],"backgroundTag":"agent-already-owned","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}