{"record":{"id":"d355fcf42f8e47cc","repo":"can1357/oh-my-pi","slug":"agent-resolvedagentid-was-replaced-during-ses","errorCode":null,"errorMessage":"Agent \"${resolvedAgentId}\" was replaced during session initialization.","messagePattern":"Agent \"(.+?)\" was replaced during session initialization\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/sdk.ts","lineNumber":3875,"sourceCode":"\t\tsession.yieldQueue.register<DeferredDiagnosticsEntry>(LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE, {\n\t\t\tbuild: buildLateDiagnosticsBatchMessage,\n\t\t\tisStale: entry => entry.isStale(),\n\t\t});\n\n\t\t// Attach the live session to the pre-registered ref so peers can route IRC\n\t\t// messages here. Refresh sessionFile in case it was unavailable at pre-register\n\t\t// time. The dispose wrapper below unregisters on teardown (unless parked).\n\t\tif (\n\t\t\t!registeredAgentRef ||\n\t\t\t!agentRegistry.attachSession(\n\t\t\t\tresolvedAgentId,\n\t\t\t\tsession,\n\t\t\t\tsessionManager.getSessionFile() ?? null,\n\t\t\t\tregisteredAgentRef,\n\t\t\t) ||\n\t\t\t!agentRegistry.setStatus(resolvedAgentId, \"running\", registeredAgentRef)\n\t\t) {\n\t\t\tthrow new Error(`Agent \"${resolvedAgentId}\" was replaced during session initialization.`);\n\t\t}\n\t\thasRegistered = true;\n\t\t// MCP notification bridge cleanup — assigned when the bridge is wired below,\n\t\t// invoked from the dispose wrapper AND registered as a postmortem so both\n\t\t// explicit-dispose (SDK embedders that reuse the process across sessions) and\n\t\t// process-exit paths tear the listener down. Nulled after use so the closure\n\t\t// graph (`extensionRunner`, `session`) can be GC'd instead of retained by the\n\t\t// process-global postmortem list.\n\t\tlet unsubscribeMcpNotifications: (() => void) | undefined;\n\t\tlet unregisterMcpPostmortem: (() => void) | undefined;\n\n\t\t{\n\t\t\tconst originalDispose = session.dispose.bind(session);\n\t\t\tsession.dispose = async () => {\n\t\t\t\ttry {\n\t\t\t\t\t// Reject new session work (eval starts) the moment disposal\n\t\t\t\t\t// begins — the lifecycle await below opens an async gap before\n\t\t\t\t\t// AgentSession.dispose() would otherwise set its guards.","sourceCodeStart":3857,"sourceCodeEnd":3893,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/sdk.ts#L3857-L3893","documentation":"At the end of session initialization the SDK atomically attaches the session and flips the registry status to \"running\" using the captured registeredAgentRef. If either compare-and-swap fails, another generation replaced the agent's registry entry while construction was in progress, and the SDK refuses to continue on a stale ownership token.","triggerScenarios":"A concurrent createAgentSession/dispose for the same resolvedAgentId changes the registry entry between the initial registration and the final setStatus(\"running\") call.","commonSituations":"Two processes racing to start a session for the same agent; an automation script launching sessions in parallel; a session being disposed/restarted at the same moment a new one initializes.","solutions":["Serialize session creation so only one process initializes a given agent at a time","Retry createAgentSession after the competing operation completes","Use distinct agent ids per concurrent session"],"exampleFix":"// before\nawait Promise.all([start(id), start(id)]);\n// after\nconst lock = await acquireAgentLock(id);\ntry { await start(id); } finally { await lock.release(); }","handlingStrategy":"retry","validationCode":"null","typeGuard":"null","tryCatchPattern":"try {\n  session = await createAgentSession({ agentId });\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"was replaced during session initialization\")) {\n    session = await createAgentSession({ agentId }); // retry once after race settles\n  } else throw err;\n}","preventionTips":["Serialize session creation per agent id across processes","Avoid disposing/restarting a session while another initialization for the same id is in flight","Use unique agent ids for parallel sessions"],"tags":["concurrency","race-condition","session"],"backgroundTag":"concurrent-modification-race","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}