shareAI-lab/learn-claude-code · error · ValueError

only bash can run in the background

Error message

only bash can run in the background

What it means

start_background_task() executes a tool block in a worker thread, but only the 'bash' tool is eligible: any other tool name (read, edit, mcp__*, ...) raises this inside the worker, so the background entry is marked failed with 'Error: ValueError: only bash can run in the background'. Long-running work is exclusively shell commands by design.

Source

Thrown at s15_integrated_harness/code.py:2108

def should_run_background(tool_name: str, tool_input: dict) -> bool:
    return (
        tool_name == "bash"
        and tool_input.get("run_in_background") is True
    )


def start_background_task(block, handlers: dict) -> str:
    global _bg_counter
    _bg_counter += 1
    bg_id = f"bg_{_bg_counter:04d}"
    command = block.input.get("command", block.name)
    cwd, cwd_error = _agent_cwd()

    def worker():
        try:
            if block.name != "bash":
                raise ValueError("only bash can run in the background")
            if cwd_error:
                raise ValueError(cwd_error.removeprefix("Error: "))
            output, exit_code = _run_bash_process(
                str(block.input["command"]), cwd)
            result = _format_bash_result(output, exit_code)
            status = "completed" if exit_code == 0 else "failed"
        except Exception as exc:
            result = f"Error: {type(exc).__name__}: {exc}"
            status = "failed"
        trigger_hooks("PostToolUse", block, result)
        with background_lock:
            background_tasks[bg_id]["status"] = status
            background_results[bg_id] = str(result)

    with background_lock:
        background_tasks[bg_id] = {
            "tool_use_id": block.id,
            "command": command,

View on GitHub (pinned to 985456f4ad)

Solutions

  1. Run only bash commands in the background; run other tools synchronously.
  2. For slow non-bash operations, wrap them in a shell command (e.g. curl) if policy permits.
  3. Gate the background option on block.name == 'bash' in the caller before invoking start_background_task.

Example fix

// before
start_background_task(read_block, handlers)  // block.name == 'read'

// after
if block.name == "bash":
    start_background_task(block, handlers)
else:
    run_sync(block, handlers)
Defensive patterns

Strategy: validation

Validate before calling

def can_background(block) -> bool:
    return block.name == "bash"

Type guard

def is_bash_block(block) -> bool:
    return getattr(block, "name", None) == "bash"

Prevention

When it happens

Trigger: Requesting background execution for a non-bash block — e.g. marking a read_file or MCP tool call as background/async; a scheduler that routes every slow tool through start_background_task; block.name mutated between validation and dispatch.

Common situations: Agents trying to background a slow MCP fetch; wrappers that set a background flag generically on all tool calls; version change where the flag started applying to more tools.

Related errors


AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14). Data as JSON: /api/errors/0b8abbcfc2b74b26. Report an issue: GitHub.