github/copilot-sdk · error · TypeError

tool filter must be a ToolSet or list[str], not str. Pass a…

Error message

tool filter must be a ToolSet or list[str], not str. Pass a single-element list (e.g. ["builtin:bash"]) or a ToolSet (e.g. ToolSet().add_builtin('bash')).

What it means

The tool filter for create_session/resume_session must be a ToolSet or a list of strings — a bare string is rejected with guidance on the correct forms. This is an explicit anti-footgun: a single string like 'builtin:bash' would otherwise be iterated character-by-character into a meaningless list of filters.

Solutions

  1. Wrap the string in a list: tools=['builtin:bash']
  2. Prefer the typed API: ToolSet().add_builtin('bash') passed as the filter
  3. Update legacy call sites that passed a single tool name string to use the list/ToolSet form
  4. Coerce defensively in shared helpers: tools = [tools] if isinstance(tools, str) else tools

Example fix

// before
client.create_session(tools="builtin:bash")

// after
client.create_session(tools=["builtin:bash"])
# or
client.create_session(tools=ToolSet().add_builtin("bash"))
Defensive patterns

Strategy: type-guard

Validate before calling

if isinstance(tools, str):
    raise TypeError("pass a list[str] or ToolSet, not a bare string")

Type guard

def is_valid_tool_filter(value) -> bool:
    return value is None or isinstance(value, (ToolSet, list))

Try / catch

try:
    session = client.create_session(tools=tools)
except TypeError as e:
    if "not str" in str(e):
        session = client.create_session(tools=[tools])

Prevention

When it happens

Trigger: Calling create_session(tools='builtin:bash') or resume_session(available_tools="mcp:*") — passing a str instead of ['builtin:bash'] or ToolSet().add_builtin('bash').

Common situations: Developers pass a single tool name directly because one tool is all they need, or copy an example that used a list but pass a string built from a variable; older code that predates the ToolSet API may still pass strings.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09). Data as JSON: /api/errors/cae88f7fb4faabe3. Report an issue: GitHub.

Appendix: source

Thrown at python/copilot/_mode.py:121

    "list_agents",
    "send_inbox",
    "context_board",
    "skill",
]


def _normalize_tool_filter(value: Any) -> list[str] | None:
    """Accept ``ToolSet``, ``list[str]``, or ``None``; return a list or ``None``.

    Reject plain ``str`` explicitly — ``list("foo")`` would silently shred it
    into characters, sending an invalid tool filter list on the wire.
    """
    if value is None:
        return None
    if isinstance(value, ToolSet):
        return value.to_list()
    if isinstance(value, str):
        raise TypeError(
            "tool filter must be a ToolSet or list[str], not str. "
            'Pass a single-element list (e.g. ["builtin:bash"]) or a '
            "ToolSet (e.g. ToolSet().add_builtin('bash'))."
        )
    return list(value)


def _validate_tool_filter_list(field: str, items: list[str] | None) -> None:
    """Reject bare ``"*"`` entries (must use ``builtin:*``/``mcp:*``/``custom:*``)."""
    if items is None:
        return
    for entry in items:
        if entry == "*":
            raise ValueError(
                f"invalid {field} entry '*': there is no bare wildcard. "
                "Use ToolSet().add_builtin('*'), .add_mcp('*'), or "
                ".add_custom('*') to target a specific source."
            )

View on GitHub (pinned to cd8cf15dc3)