can1357/oh-my-pi · error · Error

Debug session ${root.id} is still active. Terminate it befor

Error message

Debug session ${root.id} is still active. Terminate it before launching another.

What it means

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.

Source

Thrown at packages/coding-agent/src/dap/session.ts:1331

				session.dataBreakpoints = root.dataBreakpoints.map(entry => ({ ...entry }));
			} catch (error) {
				logger.debug("Failed to bind data breakpoints in child debug session", {
					sessionId: session.id,
					error: toErrorMessage(error),
				});
			}
		}
	}

	async #ensureLaunchSlot(): Promise<void> {
		for (const session of [...this.#sessions.values()]) {
			if (session.status === "terminated" || !session.client.isAlive()) {
				this.#disposeSession(session);
			}
		}
		const root = [...this.#sessions.values()].find(session => !session.parentSessionId);
		if (!root) return;
		throw new Error(`Debug session ${root.id} is still active. Terminate it before launching another.`);
	}

	#registerSession(
		client: DapClient,
		adapter: DapResolvedAdapter,
		cwd: string,
		program?: string,
		parentSessionId?: string,
	): DapSession {
		const session: DapSession = {
			id: `debug-${++this.#nextId}`,
			adapter,
			cwd,
			program,
			client,
			status: "launching",
			launchedAt: Date.now(),
			lastUsedAt: Date.now(),

View on GitHub (pinned to 9690622007)

Solutions

  1. Terminate the existing root session first (terminate/disconnect tool or manager call), then launch
  2. If the adapter hung, force-dispose: check isAlive() and kill the adapter process / dispose the session
  3. Enumerate existing sessions and pick the active one instead of launching a duplicate
  4. Make launch flows idempotent: reuse the active session if the same program/config is requested

Example fix

// before
await manager.launch(config); // throws if a session is active
// after
const existing = manager.listSessions().find(s => !s.parentSessionId);
if (existing && existing.status !== 'terminated') {
  await manager.terminate(existing.id);
}
await manager.launch(config);
Defensive patterns

Strategy: try-catch

Validate before calling

const rootActive = manager.listSessions().some(s => !s.parentSessionId && s.status !== 'terminated');
if (rootActive) await manager.terminate(rootActive.id);

Try / catch

try {
  await manager.launch(config);
} catch (err) {
  if (String((err as Error).message).includes('is still active')) {
    const root = manager.listSessions().find(s => !s.parentSessionId);
    if (root) await manager.terminate(root.id);
    await manager.launch(config);
  } else { throw err; }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/208d26e5296324a5. Report an issue: GitHub.