{"record":{"id":"208d26e5296324a5","repo":"can1357/oh-my-pi","slug":"debug-session-root-id-is-still-active-terminat","errorCode":null,"errorMessage":"Debug session ${root.id} is still active. Terminate it before launching another.","messagePattern":"Debug session (.+?) is still active\\. Terminate it before launching another\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/dap/session.ts","lineNumber":1331,"sourceCode":"\t\t\t\tsession.dataBreakpoints = root.dataBreakpoints.map(entry => ({ ...entry }));\n\t\t\t} catch (error) {\n\t\t\t\tlogger.debug(\"Failed to bind data breakpoints in child debug session\", {\n\t\t\t\t\tsessionId: session.id,\n\t\t\t\t\terror: toErrorMessage(error),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tasync #ensureLaunchSlot(): Promise<void> {\n\t\tfor (const session of [...this.#sessions.values()]) {\n\t\t\tif (session.status === \"terminated\" || !session.client.isAlive()) {\n\t\t\t\tthis.#disposeSession(session);\n\t\t\t}\n\t\t}\n\t\tconst root = [...this.#sessions.values()].find(session => !session.parentSessionId);\n\t\tif (!root) return;\n\t\tthrow new Error(`Debug session ${root.id} is still active. Terminate it before launching another.`);\n\t}\n\n\t#registerSession(\n\t\tclient: DapClient,\n\t\tadapter: DapResolvedAdapter,\n\t\tcwd: string,\n\t\tprogram?: string,\n\t\tparentSessionId?: string,\n\t): DapSession {\n\t\tconst session: DapSession = {\n\t\t\tid: `debug-${++this.#nextId}`,\n\t\t\tadapter,\n\t\t\tcwd,\n\t\t\tprogram,\n\t\t\tclient,\n\t\t\tstatus: \"launching\",\n\t\t\tlaunchedAt: Date.now(),\n\t\t\tlastUsedAt: Date.now(),","sourceCodeStart":1313,"sourceCodeEnd":1349,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/dap/session.ts#L1313-L1349","documentation":"The manager enforces a single active debug session tree: before launching a new root session it disposes dead sessions, then checks for any surviving root session. If one exists, launching another is rejected so the single-session invariant holds and adapter resources are not duplicated.","triggerScenarios":"Calling launch/attach for a new root session while a previous root session still exists with status not 'terminated' and a live client; a previous session's client is still alive (adapter process running) even though the user considers the run finished; attempting a second launch without terminate/disconnect of the first.","commonSituations":"User starts a new debug run without stopping the previous one; a hung adapter keeps the old session 'active' (client alive, debuggee stuck); scripts that call launch repeatedly without terminate between runs; a breakpoint hit and session paused while a new launch is attempted.","solutions":["Terminate the existing root session first (terminate/disconnect tool or manager call), then launch","If the adapter hung, force-dispose: check isAlive() and kill the adapter process / dispose the session","Enumerate existing sessions and pick the active one instead of launching a duplicate","Make launch flows idempotent: reuse the active session if the same program/config is requested"],"exampleFix":"// before\nawait manager.launch(config); // throws if a session is active\n// after\nconst existing = manager.listSessions().find(s => !s.parentSessionId);\nif (existing && existing.status !== 'terminated') {\n  await manager.terminate(existing.id);\n}\nawait manager.launch(config);","handlingStrategy":"try-catch","validationCode":"const rootActive = manager.listSessions().some(s => !s.parentSessionId && s.status !== 'terminated');\nif (rootActive) await manager.terminate(rootActive.id);","typeGuard":null,"tryCatchPattern":"try {\n  await manager.launch(config);\n} catch (err) {\n  if (String((err as Error).message).includes('is still active')) {\n    const root = manager.listSessions().find(s => !s.parentSessionId);\n    if (root) await manager.terminate(root.id);\n    await manager.launch(config);\n  } else { throw err; }\n}","preventionTips":["Always terminate the previous debug session before launching a new one","Make launch flows idempotent: reuse or restart the existing session instead of failing","Force-dispose sessions whose adapter hung (client alive but debuggee stuck)","Track session state in your tool/UI to disable launch while a session is active"],"tags":["dap","lifecycle","session-management","state-conflict"],"backgroundTag":"session-already-active","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}