can1357/oh-my-pi · warning · PythonExecutionCancelledError

Command aborted

Error message

Command aborted

What it means

`replaceSessionKernel` validates after each await that the session it is replacing the kernel for is still the live one: the session map still points at this session, the generation matches, and the kernel reference is unchanged. If any changed (the session was invalidated or raced with another replacement), it throws `PythonExecutionCancelledError(false)` with message "Command aborted". This is a stale-state guard against concurrent kernel replacement.

Source

Thrown at packages/coding-agent/src/eval/py/executor.ts:237

): Promise<PythonKernel> {
	const kernel = session.kernel;
	const generation = session.generation;
	const inFlight = session.replacement;
	if (inFlight?.generation === generation) {
		if (
			inFlight.deadlineMs !== undefined &&
			(options.deadlineMs === undefined || options.deadlineMs > inFlight.deadlineMs)
		) {
			inFlight.deadlineMs = options.deadlineMs;
		}
		return await waitForPromiseWithCancellation(inFlight.promise, options, PythonExecutionCancelledError);
	}
	if (
		context.sessions.get(session.sessionKey) !== session ||
		session.generation !== generation ||
		session.kernel !== kernel
	) {
		throw new PythonExecutionCancelledError(false);
	}

	const deferred = Promise.withResolvers<PythonKernel>();
	const replacement: SessionKernelReplacement = {
		generation,
		deadlineMs: options.deadlineMs,
		promise: deferred.promise,
	};
	session.replacement = replacement;
	void (async () => {
		try {
			const remaining = getRemainingTimeoutMs(options.deadlineMs);
			await kernel
				.shutdown(remaining !== undefined ? { timeoutMs: Math.max(0, remaining) } : undefined)
				.catch(() => undefined);
			if (replacement.deadlineMs !== undefined && replacement.deadlineMs <= Date.now()) {
				throw new PythonExecutionCancelledError(true);
			}

View on GitHub (pinned to 9690622007)

Solutions

  1. Retry `acquireLiveSessionKernel` to get the current live kernel instead of continuing with the aborted replacement
  2. Avoid concurrent operations on the same session key; serialize kernel restarts
  3. Surface the abort to the caller as a cancelled command (that is the intended contract of PythonExecutionCancelledError)

Example fix

// before: fire-and-forget replacement races
void replaceSessionKernel(...);
await kernel.execute(code);
// after
await acquireLiveSessionKernel(ctx, session, options); // await replacement before executing
Defensive patterns

Strategy: retry

Validate before calling

// before proceeding, confirm session is still current
if (context.sessions.get(session.sessionKey) !== session) {
  session = context.sessions.get(session.sessionKey)!; // re-acquire live session
}

Try / catch

try {
  kernel = await acquireLiveSessionKernel(ctx, session, options);
} catch (err) {
  if (err instanceof PythonExecutionCancelledError && !err.timedOut) {
    kernel = await acquireLiveSessionKernel(ctx, ctx.sessions.get(session.sessionKey)!, options); // retry once on current state
  } else throw err;
}

Prevention

When it happens

Trigger: During kernel replacement (from `acquireLiveSessionKernel`), after checking identity, something invalidates the session: another thread/task replaced the kernel, the session's generation was bumped, or the session was removed from `context.sessions`.

Common situations: Concurrent requests racing to restart the same Python session; a timeout/abort path bumping the generation while a replacement was in flight; session cleanup running in parallel with a restart.

Related errors


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