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

Bash command cannot be empty

Error message

Bash command cannot be empty

What it means

Raised by BackgroundManager.start() in s11_background_tasks/code.py:319 when the bash block's 'command' input is missing, not a string, or empty after stripping. The background thread will shell out with this exact string, so a blank command would spawn a no-op shell with nothing to capture. The check runs after the 'Only Bash' name check and before the task is registered and the thread starts.

Source

Thrown at s11_background_tasks/code.py:319

    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,
        )
        try:
            thread.start()
        except Exception:

View on GitHub (pinned to 985456f4ad)

Solutions

  1. Validate block.input.get('command') is a non-empty string before calling start().
  2. When the command comes from an LLM, reject/regenerate tool calls whose arguments fail a schema check rather than forwarding them.
  3. Fix template producers so an empty command is never emitted — require the variable and fail early with a clear message.

Example fix

# before
bg_id = background.start(block)

# after
command = block.input.get('command') if isinstance(block.input, dict) else None
if isinstance(command, str) and command.strip():
    bg_id = background.start(block)
else:
    raise ValueError('background start requires a non-empty bash command')
Defensive patterns

Strategy: validation

Validate before calling

def block_has_command(block) -> bool:
    cmd = block.input.get('command') if isinstance(getattr(block, 'input', None), dict) else None
    return isinstance(cmd, str) and bool(cmd.strip())

Type guard

def is_runnable_bash_block(block) -> bool:
    return (getattr(block, 'name', '') == 'bash'
            and isinstance(block.input, dict)
            and isinstance(block.input.get('command'), str)
            and bool(block.input['command'].strip()))

Try / catch

try:
    bg_id = background.start(block)
except ValueError as e:
    if 'cannot be empty' in str(e):
        ask_model_to_regenerate_tool_call(block)  # or surface to the user
    else:
        raise

Prevention

When it happens

Trigger: Passing a block whose input is {} (no 'command' key); input {'command': ''} or {'command': ' '}; input {'command': ['ls', '-l']} (a list, not a string); input built from an LLM tool call where the command argument was omitted or nulled.

Common situations: Malformed agent tool_use blocks from the model (missing arguments); templates that interpolate a possibly-empty variable into the command; refactors that changed the input schema key from 'command' to something else; UI 'run in background' submitted from an empty input field.

Related errors


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