feat(coding-agent): add async bash() to IPython kernel - #1187
Conversation
Add an async bash() function to the RLM bootstrap code in ipython.ts
that uses asyncio.create_subprocess_exec to run shell commands without
blocking the kernel event loop. Unlike %%bash cells (which block the
kernel until the command finishes), await bash('...') keeps the kernel
responsive to interrupts and other messages while the process runs.
Supports optional timeout (raises TimeoutError) and cwd parameters.
Returns a _PrimeAgentBashResult with stdout, stderr, and returncode.
Update the RLM system prompt to prefer await bash('...') over %%bash
cells.
| env = dict(_prime_agent_bash_os.environ) | ||
| work_dir = cwd or _prime_agent_bash_os.getcwd() | ||
|
|
||
| proc = await _prime_agent_asyncio.create_subprocess_exec( |
There was a problem hiding this comment.
🟠 High tools/ipython.ts:84
bash() reads both stdout and stderr entirely into memory via proc.communicate(). Commands that produce large or unbounded output (e.g., a long-running test process that continuously logs) will buffer everything in the persistent kernel's memory until it is exhausted and the kernel crashes. Consider streaming the output and enforcing a size limit instead of relying on communicate(), which Python's asyncio docs warn against for large or unlimited output.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/core/tools/ipython.ts around line 84:
`bash()` reads both stdout and stderr entirely into memory via `proc.communicate()`. Commands that produce large or unbounded output (e.g., a long-running test process that continuously logs) will buffer everything in the persistent kernel's memory until it is exhausted and the kernel crashes. Consider streaming the output and enforcing a size limit instead of relying on `communicate()`, which Python's `asyncio` docs warn against for large or unlimited output.
| env = dict(_prime_agent_bash_os.environ) | ||
| work_dir = cwd or _prime_agent_bash_os.getcwd() | ||
|
|
||
| proc = await _prime_agent_asyncio.create_subprocess_exec( |
There was a problem hiding this comment.
🟠 High tools/ipython.ts:84
On timeout, bash() kills only the Bash process while its child processes keep running. For example, await bash('sleep 60', timeout=1) reports a TimeoutError after 1 second, but the sleep child survives and continues executing — along with any file or network side effects — because proc.kill() targets only the Bash PID, not its descendants. Consider launching the subprocess in a new process group/session (start_new_session=True) and sending the signal to the entire group on timeout, e.g. os.killpg(os.getpgid(proc.pid), signal.SIGKILL).
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/core/tools/ipython.ts around line 84:
On timeout, `bash()` kills only the Bash process while its child processes keep running. For example, `await bash('sleep 60', timeout=1)` reports a `TimeoutError` after 1 second, but the `sleep` child survives and continues executing — along with any file or network side effects — because `proc.kill()` targets only the Bash PID, not its descendants. Consider launching the subprocess in a new process group/session (`start_new_session=True`) and sending the signal to the entire group on timeout, e.g. `os.killpg(os.getpgid(proc.pid), signal.SIGKILL)`.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e92f4b0. Configure here.
| except _prime_agent_asyncio.TimeoutError: | ||
| proc.kill() | ||
| await proc.wait() | ||
| raise TimeoutError(f"bash command timed out after {timeout}s: {command}") |
There was a problem hiding this comment.
Orphaned processes after timeout or interrupt
High Severity
bash() only kills the parent shell on timeout and has no cleanup if the await is cancelled or interrupted. Child processes started by the command keep running, and an interrupted await bash(...) can leave the whole subprocess alive while the kernel moves on.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit e92f4b0. Configure here.


Summary
Adds an async
bash()function to the IPython kernel bootstrap code soawait bash("...")runs shell commands viaasyncio.create_subprocess_execwithout blocking the kernel event loop.Motivation
%%bashcells block the IPython kernel until the command finishes — the kernel can't respond to interrupts or other messages while a shell command runs. This is the same anti-pattern astime.sleep()polling.await bash("...")usesasyncio.create_subprocess_execso the kernel event loop stays responsive.Changes
ipython.ts: Added_PrimeAgentBashResultclass andasync bash()function toRLM_BOOTSTRAP_BASE_CODE. Supportstimeout(raisesTimeoutError) andcwdparameters. Returns stdout, stderr, and returncode.rlm.ts: Updated the IPython control prompt to preferawait bash("...")over%%bashcells.system-prompt.test.ts: Updated exact-match block and addedtoContainassertion for the new prompt text.Usage
Testing
npx vitest run test/system-prompt.test.ts— 25/25 passnpx vitest run test/ipython-bootstrap.test.ts— 6/6 pass (including real kernel tests)npm run check— cleanNote
Medium Risk
Changes how agents run shell from IPython (new default path) and executes arbitrary shell via subprocess in the kernel; behavior is additive with
%%bashstill supported, but long-running commands and env/cwd semantics may differ from magic cells.Overview
Introduces
await bash("...")in the IPython kernel bootstrap (RLM_BOOTSTRAP_BASE_CODE): commands run throughasyncio.create_subprocess_execwith optionaltimeoutandcwd, returning a_PrimeAgentBashResult(stdout, stderr, returncode) instead of blocking the kernel like%%bashcells.RLM / system prompt guidance now prefers
await bash(...)for shell work while still documenting%%bashrules when cells are used. CHANGELOG andsystem-prompt.test.tsare updated to match (including aprefer \await bash`` assertion).Reviewed by Cursor Bugbot for commit e92f4b0. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add async
bash()function to IPython kernel for non-blocking shell executionbash(command, *, timeout, cwd)function to the IPython kernel bootstrap in ipython.ts that spawns shell commands viaasyncio.create_subprocess_execwithout blocking the event loop._PrimeAgentBashResultobject withstdout,stderr, andreturncodefields; stringifies to combined output with exit-code info on failure.await bash("...")over%%bashmagic commands.📊 Macroscope summarized e92f4b0. 3 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted
🗂️ Filtered Issues
No issues evaluated.