can1357/oh-my-pi · error · Error

DAP adapter ${this.adapter.name} is not running

Error message

DAP adapter ${this.adapter.name} is not running

What it means

DapClient.sendRequest refuses to send a DAP request over a transport that has already been disposed. Once #disposed is set (after disconnect/termination of the client), any further request — initialize, scopes, continue, etc. — cannot reach the adapter and the client throws immediately instead of hanging until the request timeout. It guards callers against writing into a dead pipe.

Source

Thrown at packages/coding-agent/src/dap/client.ts:444

		}
		timeout = setTimeout(() => {
			cleanup();
			reject(new Error(`DAP event ${event} timed out after ${timeoutMs}ms`));
		}, timeoutMs);
		return promise;
	}

	async sendRequest<TBody = unknown>(
		command: string,
		args?: unknown,
		signal?: AbortSignal,
		timeoutMs: number = DEFAULT_REQUEST_TIMEOUT_MS,
	): Promise<TBody> {
		if (signal?.aborted) {
			throw signal.reason instanceof Error ? signal.reason : new ToolAbortError();
		}
		if (this.#disposed) {
			throw new Error(`DAP adapter ${this.adapter.name} is not running`);
		}
		const requestSeq = ++this.#requestSeq;
		const request: DapRequestMessage = {
			seq: requestSeq,
			type: "request",
			command,
			arguments: args,
		};
		const { promise, resolve, reject } = Promise.withResolvers<TBody>();
		// Suppress "unhandled rejection" if the request timer or abort fires
		// before the caller's `await` subscribes — e.g. while #writeMessage is
		// still racing a wedged stdin flush. The caller's own `await` still
		// receives the rejection normally; this handler is a passive guard.
		promise.catch(() => {});

		let timeout: NodeJS.Timeout | undefined;
		const cleanup = () => {
			if (timeout) clearTimeout(timeout);

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-obtain a live session: re-launch or re-attach the debug session before issuing further requests
  2. Check client.isAlive() (or catch this error) before reusing a stored DapSession/DapClient reference
  3. Make sure no concurrent code path calls disconnect/terminate while requests are still in flight; serialize session teardown with pending requests
  4. If you keep a reference across awaits, re-fetch the active session from DapSessionManager instead of caching the client

Example fix

// before
const scopes = await cachedClient.sendRequest('scopes', { frameId });
// after
if (!cachedClient.isAlive()) {
  session = await manager.launch(config); // recreate the session
}
const scopes = await session.client.sendRequest('scopes', { frameId });
Defensive patterns

Strategy: try-catch

Validate before calling

if (!client.isAlive()) {
  client = await DapClient.connect({ adapter, cwd });
}

Type guard

function isUsableClient(c: DapClient | null | undefined): c is DapClient {
  return !!c && c.isAlive();
}

Try / catch

try {
  return await client.sendRequest(command, args, { signal });
} catch (err) {
  if (String((err as Error).message).includes('is not running')) {
    client = await DapClient.connect({ adapter, cwd }); // recreate
    return await client.sendRequest(command, args, { signal });
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling any request method on a DapClient after disconnect() or after the session was terminated/disposed; reusing a stale DapSession whose client was disposed when the adapter exited; a race where an abort check passes but disposal happens between it and the disposed check.

Common situations: A debug adapter crashed or was terminated and the tool then issues follow-up commands (scopes, variables, stack_trace); a queued async task continues after the session manager disposed the session on adapter exit; a UI keeps issuing evaluate/continue calls after the user stopped debugging.

Related errors


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