Background process management for LLM coding agents
Non-blocking bash with streaming output, stdin, and long-polling
Your agent runs npm run build and waits. Does nothing. Watches output scroll by. Wastes tokens.
better-bash fixes that. Five tools that turn blocking bash into background process management: the agent starts a command, does other work, checks back when it's ready.
Add to ~/.config/opencode/opencode.json:
{
"plugin": ["better-bash"]
}Add to ~/.config/mimocode/mimocode.json:
{
"plugin": ["better-bash"]
}git clone https://github.com/alper-dev/better-bash.git
cd better-bash
bun install && bun run build| Tool | Does |
|---|---|
bash_start |
Start a command in the background |
bash_status |
Check status, get new output (long-poll supported) |
bash_kill |
Kill a running process |
bash_stdin |
Write to a process's stdin |
bash_list |
List all running processes |
Without better-bash:
agent: bash("npm run build")
agent: ... waits 45 seconds doing nothing ...
agent: "Build complete!"
With better-bash:
agent: bash_start("npm run build")
→ { id: "pk7qx", pid: 1234 }
agent: bash_status("pk7qx")
→ { status: "running", newOutput: "compiling 42/100..." }
agent: (reviews a PR while the build runs)
agent: bash_status("pk7qx", { wait: 5000 })
→ { status: "done", newOutput: "...built in 12s", exitCode: 0 }
bash_start("pytest tests/ -v")
→ { id: "pm2va", pid: 4521 }
# Agent edits src/auth.py while tests run
bash_status("pm2va", { wait: 30000 })
→ { status: "done", newOutput: "48 passed, 2 failed in 12.3s", exitCode: 1 }
# Agent sees which tests failed, fixes them
bash_status("pm2va", { all: true })
→ { newOutput: "FAILED tests/test_login.py::test_expired_token..." }
bash_start("npm run dev")
→ { id: "pq9dx", pid: 8834 }
bash_status("pq9dx", { wait: 10000 })
→ { status: "running", newOutput: "Server running on http://localhost:3000" }
# Server is ready, agent can now test endpoints
bash_start("npx prisma migrate deploy")
→ { id: "pz4mn", pid: 2291 }
bash_status("pz4mn", { wait: 15000 })
→ { status: "running", newOutput: "Applying migration 20240101_add_users..." }
bash_status("pz4mn")
→ { status: "done", newOutput: "3 migrations applied successfully.", exitCode: 0 }
bash_start("psql -U postgres mydb")
→ { id: "pa8kx", pid: 3301 }
bash_stdin("pa8kx", "SELECT count(*) FROM users WHERE active = true;")
bash_status("pa8kx")
→ { newOutput: " count\n-------\n 847\n(1 row)" }
Send input to running processes, useful for interactive scripts, REPLs, and CLIs:
bash_start("python manage.py shell")
→ { id: "ph3tq", pid: 6612 }
bash_stdin("ph3tq", "from django.contrib.auth.models import User")
bash_stdin("ph3tq", "User.objects.filter(is_active=False).count()")
bash_status("ph3tq")
→ { newOutput: ">>> 42" }
Newline is auto-appended if missing.
PowerShell uses Base64-encoded commands. Rules:
- Use single quotes:
echo 'hello world' - Avoid double quotes unless needed; PowerShell escapes with backtick (
`), not backslash ; | & < > { }work fine for chaining$HOMEexpands, so wrap in single quotes to prevent
| Consumer | Works | Notes |
|---|---|---|
Python input() |
Yes | Newline auto-appended |
Python readline() |
Yes | Newline auto-appended |
Python read() |
Yes | Set eof: true after optional data |
Python readlines() |
Yes | Set eof: true after optional data |
PowerShell Read-Host |
Yes | |
PowerShell $input pipe |
No | Known limitation |
| Param | Type | Default | Description |
|---|---|---|---|
command |
string | required | Shell command |
workdir |
string | cwd | Working directory |
timeout |
number | 0 | Process timeout in ms; 0 disables timeout (unlimited) |
Returns: { id, pid, status }
| Param | Type | Default | Description |
|---|---|---|---|
id |
string | required | Process ID |
maxOutput |
number | 30000 | Max bytes to return |
wait |
number | 0 | Long-poll timeout in ms |
all |
boolean | false | Reset pagination and return output from beginning |
Returns: { id, status, exitCode, newOutput, totalOutputSize, truncated, hasMore, elapsed }
Statuses: running done killed error timed_out
Running process: "Process X terminated."
Already exited: "Process X already exited with status 'done' (exit code 0)."
Write optional data to stdin. Newline auto-appended if missing. Set eof: true to close stdin after data and signal EOF. Close-only call: bash_stdin({ id: "pk7qx", eof: true }).
Returns all processes with ID, PID, command, status, exit code, output size, elapsed time.
bun install
bun test
bun run build # dist/
bun run typecheckMIT