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
- Retry `acquireLiveSessionKernel` to get the current live kernel instead of continuing with the aborted replacement
- Avoid concurrent operations on the same session key; serialize kernel restarts
- 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
- Serialize per-session kernel operations with a lock/queue
- Don't fire kernel replacement fire-and-forget
- Re-resolve the session from context.sessions after any await
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
- Command timed out
- Python kernel unavailable
- Python execution is unavailable while session disposal is in
- Failed to dispose one or more eval kernels
- operation canceled
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/be178c02b5444f43.
Report an issue: GitHub.