earendil-works/pi · error · AggregateError

Failed to dispose remote session

Error message

Failed to dispose remote session

What it means

Aggregation error from settleRemoteSessionDisposal: after disposing a remote session, one or more cleanup promises (session lease disposal, pending attachment operations) rejected with errors other than the expected RemoteSessionDisposedError. Multiple failures are wrapped in an AggregateError.

Solutions

  1. Inspect the aggregated causes; the first rejection is usually the transport or server-side dispose failure
  2. Retry dispose if the failure was a transient connection drop
  3. Report a bug if cleanup reproducibly fails with a healthy connection
Defensive patterns

Strategy: try-catch

When it happens

Trigger: Thrown at packages/coding-agent/src/client/remote-session.ts:63 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of earendil-works/pi@4af9d21d3b (2026-08-24). Data as JSON: /api/errors/813f4764873d8f8c. Report an issue: GitHub.

Appendix: source

Thrown at packages/coding-agent/src/client/remote-session.ts:63

export interface RemoteSessionOptions {
	onListenerError?: (error: Error) => void;
}

class RemoteSessionDisposedError extends Error {
	constructor() {
		super("Remote session is disposed");
		this.name = "RemoteSessionDisposedError";
	}
}

async function settleRemoteSessionDisposal(cleanup: readonly Promise<void>[]): Promise<void> {
	const results = await Promise.allSettled(cleanup);
	const errors = results.flatMap((result) =>
		result.status === "rejected" && !(result.reason instanceof RemoteSessionDisposedError) ? [result.reason] : [],
	);
	if (errors.length === 1) throw errors[0];
	if (errors.length > 1) throw new AggregateError(errors, "Failed to dispose remote session");
}

export class RemoteSession {
	readonly #client: PiClient;
	readonly #onListenerError: ((error: Error) => void) | undefined;
	#lifecycle: RemoteSessionLifecycle = { status: "unbound" };
	#handle: SessionLease | undefined;
	#transcript: TranscriptState | undefined;
	#unsubscribeSnapshot: Unsubscribe | undefined;
	#unsubscribeEvents: Unsubscribe | undefined;
	readonly #listeners = new Set<(state: RemoteSessionState) => void>();
	readonly #pendingAttachmentOperations = new Set<Promise<void>>();
	readonly #activeOperationStates = new Set<RemoteSessionLifecycle>();
	#disposePromise: Promise<void> | undefined;
	#resolveDisposeSignal: () => void = () => {};
	readonly #disposeSignal = new Promise<void>((resolve) => {
		this.#resolveDisposeSignal = resolve;
	});

View on GitHub (pinned to 4af9d21d3b)