FoundationAgents/OpenManus · warning · ToolError

Parameter `steps` must be a list of strings for command: upd

Error message

Parameter `steps` must be a list of strings for command: update

What it means

Raised by _update_plan (app/tool/planning.py:179) when the steps argument is provided (truthy) but is not a list of strings. Note the difference from create: empty/None steps is fine on update (it just leaves steps unchanged); only a truthy, wrongly-typed steps value is rejected. Validation fires before statuses/notes are recomputed, so the existing plan is untouched.

Source

Thrown at app/tool/planning.py:179

        self, plan_id: Optional[str], title: Optional[str], steps: Optional[List[str]]
    ) -> ToolResult:
        """Update an existing plan with new title or steps."""
        if not plan_id:
            raise ToolError("Parameter `plan_id` is required for command: update")

        if plan_id not in self.plans:
            raise ToolError(f"No plan found with ID: {plan_id}")

        plan = self.plans[plan_id]

        if title:
            plan["title"] = title

        if steps:
            if not isinstance(steps, list) or not all(
                isinstance(step, str) for step in steps
            ):
                raise ToolError(
                    "Parameter `steps` must be a list of strings for command: update"
                )

            # Preserve existing step statuses for unchanged steps
            old_steps = plan["steps"]
            old_statuses = plan["step_statuses"]
            old_notes = plan["step_notes"]

            # Create new step statuses and notes
            new_statuses = []
            new_notes = []

            for i, step in enumerate(steps):
                # If the step exists at the same position in old steps, preserve status and notes
                if i < len(old_steps) and step == old_steps[i]:
                    new_statuses.append(old_statuses[i])
                    new_notes.append(old_notes[i])
                else:

View on GitHub (pinned to 52a13f2a57)

Solutions

  1. Send a plain list of str: steps=['Install', 'Test'].
  2. Omit steps entirely (or pass None) when only updating the title.
  3. Sanitize at the boundary: steps = [str(s) for s in steps] if steps is not None else None, dropping non-serializable entries before the call.

Example fix

// before
await planning.execute(command='update', plan_id='p1', steps='Install, Test')

// after
await planning.execute(command='update', plan_id='p1', steps=['Install', 'Test'])
Defensive patterns

Strategy: type-guard

Validate before calling

if steps is not None:
    steps = [str(s) for s in steps if isinstance(s, str) and s.strip()] or None
await planning.execute(command='update', plan_id=plan_id, title=title, steps=steps)

Type guard

from typing import TypeGuard
def is_optional_step_list(v: object) -> TypeGuard[list[str] | None]:
    return v is None or (isinstance(v, list) and all(isinstance(s, str) for s in v))

Try / catch

try:
    await planning.execute(command='update', plan_id=pid, steps=s)
except ToolError as e:
    if 'steps` must be a list of strings' in str(e):
        await planning.execute(command='update', plan_id=pid, steps=[str(x) for x in s])
    else:
        raise

Prevention

When it happens

Trigger: Calling update with steps='a, b, c' (string), steps=['x', None], steps=('x','y') (tuple from some parsers is actually a list-compatible failure only if elements are non-str; tuples fail isinstance(list)), or a dict of step->status pairs.

Common situations: LLM callers formatting steps as a comma-separated string; steps sourced from JSON that contains numbers or nulls; reusable code paths shared with create where the stricter non-empty rule trained users to always send steps, sometimes in the wrong shape.

Related errors


AI-assisted analysis of FoundationAgents/OpenManus@52a13f2a57 (2026-08-15). Data as JSON: /api/errors/a01676afa48089ce. Report an issue: GitHub.