{"record":{"id":"be178c02b5444f43","repo":"can1357/oh-my-pi","slug":"command-aborted","errorCode":null,"errorMessage":"Command aborted","messagePattern":"Command aborted","errorType":"exception","errorClass":"PythonExecutionCancelledError","httpStatus":null,"severity":"warning","filePath":"packages/coding-agent/src/eval/py/executor.ts","lineNumber":237,"sourceCode":"): Promise<PythonKernel> {\n\tconst kernel = session.kernel;\n\tconst generation = session.generation;\n\tconst inFlight = session.replacement;\n\tif (inFlight?.generation === generation) {\n\t\tif (\n\t\t\tinFlight.deadlineMs !== undefined &&\n\t\t\t(options.deadlineMs === undefined || options.deadlineMs > inFlight.deadlineMs)\n\t\t) {\n\t\t\tinFlight.deadlineMs = options.deadlineMs;\n\t\t}\n\t\treturn await waitForPromiseWithCancellation(inFlight.promise, options, PythonExecutionCancelledError);\n\t}\n\tif (\n\t\tcontext.sessions.get(session.sessionKey) !== session ||\n\t\tsession.generation !== generation ||\n\t\tsession.kernel !== kernel\n\t) {\n\t\tthrow new PythonExecutionCancelledError(false);\n\t}\n\n\tconst deferred = Promise.withResolvers<PythonKernel>();\n\tconst replacement: SessionKernelReplacement = {\n\t\tgeneration,\n\t\tdeadlineMs: options.deadlineMs,\n\t\tpromise: deferred.promise,\n\t};\n\tsession.replacement = replacement;\n\tvoid (async () => {\n\t\ttry {\n\t\t\tconst remaining = getRemainingTimeoutMs(options.deadlineMs);\n\t\t\tawait kernel\n\t\t\t\t.shutdown(remaining !== undefined ? { timeoutMs: Math.max(0, remaining) } : undefined)\n\t\t\t\t.catch(() => undefined);\n\t\t\tif (replacement.deadlineMs !== undefined && replacement.deadlineMs <= Date.now()) {\n\t\t\t\tthrow new PythonExecutionCancelledError(true);\n\t\t\t}","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/eval/py/executor.ts#L219-L255","documentation":"`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.","triggerScenarios":"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`.","commonSituations":"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.","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)"],"exampleFix":"// before: fire-and-forget replacement races\nvoid replaceSessionKernel(...);\nawait kernel.execute(code);\n// after\nawait acquireLiveSessionKernel(ctx, session, options); // await replacement before executing","handlingStrategy":"retry","validationCode":"// before proceeding, confirm session is still current\nif (context.sessions.get(session.sessionKey) !== session) {\n  session = context.sessions.get(session.sessionKey)!; // re-acquire live session\n}","typeGuard":null,"tryCatchPattern":"try {\n  kernel = await acquireLiveSessionKernel(ctx, session, options);\n} catch (err) {\n  if (err instanceof PythonExecutionCancelledError && !err.timedOut) {\n    kernel = await acquireLiveSessionKernel(ctx, ctx.sessions.get(session.sessionKey)!, options); // retry once on current state\n  } else throw err;\n}","preventionTips":["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"],"tags":["python","kernel","race-condition","cancellation"],"backgroundTag":"stale-session-generation","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}