shareAI-lab/learn-claude-code · error · ValueError
Only Bash commands can run in the background
Error message
Only Bash commands can run in the background
What it means
Raised by BackgroundManager.start() in s11_background_tasks/code.py:316 when asked to run a tool block whose name is not 'bash'. Background execution in s11 is implemented by shelling out the command in a worker thread, so only Bash command blocks — which carry a 'command' string to execute — are supported. The check is an exact string comparison on block.name before any input validation.
Source
Thrown at s11_background_tasks/code.py:316
output = handler(**block.input) if handler else f"Unknown: {block.name}"
except Exception as error:
output = f"Error: {error}"
return str(output)
# -- New in s11: background execution --
class BackgroundManager:
def __init__(self):
self.tasks: dict[str, dict] = {}
self.results: dict[str, str] = {}
self._ready: list[str] = []
self._counter = 0
self._lock = threading.Lock()
def start(self, block) -> str:
if block.name != "bash":
raise ValueError("Only Bash commands can run in the background")
command = block.input.get("command")
if not isinstance(command, str) or not command.strip():
raise ValueError("Bash command cannot be empty")
with self._lock:
self._counter += 1
task_id = f"bg_{self._counter:04d}"
self.tasks[task_id] = {
"tool_use_id": block.id,
"command": command,
"status": "running",
}
thread = threading.Thread(
target=self._run,
args=(task_id, command),
daemon=True,
)View on GitHub (pinned to 985456f4ad)
Solutions
- Only call start() with blocks whose name is exactly 'bash'; gate the call with `if block.name == 'bash':`.
- For non-bash tools, execute them synchronously through their normal handler instead of the background manager.
- If you added a new shell-like tool, extend the check to accept it explicitly rather than bypassing it.
Example fix
# before
bg_id = background.start(block) # any tool block
# after
if block.name == 'bash':
bg_id = background.start(block)
else:
result = run_tool_sync(block) Defensive patterns
Strategy: type-guard
Validate before calling
def block_is_backgroundable(block) -> bool:
return getattr(block, 'name', None) == 'bash' Type guard
def is_bash_block(block) -> bool:
return getattr(block, 'name', '') == 'bash' Try / catch
try:
bg_id = background.start(block)
except ValueError as e:
if 'Only Bash' in str(e):
result = run_tool_sync(block) # fall back to synchronous execution
else:
raise Prevention
- Filter tool blocks by name before deciding background vs. synchronous.
- Keep the tool-name constant shared between the dispatcher and the background manager.
- If you add shell-like tools, whitelist them explicitly rather than catching the error.
When it happens
Trigger: Passing a tool-call block whose .name is 'read', 'edit', 'write', 'grep', or any non-'bash' tool to BackgroundManager.start(); routing agent tool_use blocks to the background manager without filtering by name; constructing a synthetic block dict/object with the wrong name casing ('Bash').
Common situations: A dispatcher that forwards all tool calls to the background manager for uniformity; agent code that decides to 'run this in the background' for an edit or read operation; refactors that rename the bash tool constant without updating this comparison.
Related errors
- Bash command cannot be empty
- only bash can run in the background
- background result cannot be empty
- Max 20 todos allowed
- Item {item_id}: text required
AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14).
Data as JSON: /api/errors/08ea1cc8f4d5a342.
Report an issue: GitHub.