huggingface/smolagents · error · InterpreterError

Forbidden call to dunder function: {func.__name__}

Error message

Forbidden call to dunder function: {func.__name__}

What it means

Calls to dunder (double-underscore) methods are forbidden unless the name appears in static_tools or the interpreter's ALLOWED_DUNDER_METHODS whitelist. This prevents sandbox escapes via tricks like obj.__class__.__bases__[0].__subclasses__() or .__reduce__().

Source

Thrown at src/smolagents/local_python_executor.py:917

            return super(cls, instance)
        else:
            raise InterpreterError("super() takes at most 2 arguments")
    elif func_name == "print":
        state["_print_outputs"] += " ".join(map(str, args)) + "\n"
        return None
    else:  # Assume it's a callable object
        if (inspect.getmodule(func) == builtins) and inspect.isbuiltin(func) and (func not in static_tools.values()):
            raise InterpreterError(
                f"Invoking a builtin function that has not been explicitly added as a tool is not allowed ({func_name})."
            )
        if (
            hasattr(func, "__name__")
            and func.__name__.startswith("__")
            and func.__name__.endswith("__")
            and (func.__name__ not in static_tools)
            and (func.__name__ not in ALLOWED_DUNDER_METHODS)
        ):
            raise InterpreterError(f"Forbidden call to dunder function: {func.__name__}")
        return func(*args, **kwargs)


def evaluate_subscript(
    subscript: ast.Subscript,
    state: dict[str, Any],
    static_tools: dict[str, Callable],
    custom_tools: dict[str, Callable],
    authorized_imports: list[str],
) -> Any:
    index = evaluate_ast(subscript.slice, state, static_tools, custom_tools, authorized_imports)
    value = evaluate_ast(subscript.value, state, static_tools, custom_tools, authorized_imports)
    try:
        return value[index]
    except (KeyError, IndexError, TypeError) as e:
        error_message = f"Could not index {value} with '{index}': {type(e).__name__}: {e}"
        if isinstance(index, str) and isinstance(value, Mapping):
            close_matches = difflib.get_close_matches(index, list(value.keys()))

View on GitHub (pinned to 30bb116109)

Solutions

  1. Use the equivalent syntactic sugar (len(x), x[0], iter(x)) instead of calling the dunder
  2. If a dunder legitimately must be callable, add its name to the tools/static_tools you configure
  3. Avoid introspection chains like __class__/__subclasses__ in generated code

Example fix

# before
item = seq.__getitem__(0)
# after
item = seq[0]
Defensive patterns

Strategy: fallback

Validate before calling

# normalize generated code: replace dunder calls with syntax sugar
import re
code = re.sub(r'\.__len__\(\)', '', code)
code = re.sub(r'(\w+)\.__getitem__\(([^)]+)\)', r'\1[\2]', code)

Try / catch

try:
    evaluate_python(code, ...)
except InterpreterError as e:
    if 'dunder' in str(e):
        # rewrite the offending dunder call to sugar form and retry

Prevention

When it happens

Trigger: Writing an explicit call to a dunder: x.__len__(), obj.__getitem__(0), getattr-style access evaluated to __globals__ or __reduce__, or calling __init__ directly.

Common situations: Agent code or prompt-injected output tries to reach internals for a sandbox escape; using dunder-call style instead of syntax sugar (x[0] instead of x.__getitem__(0)); calling __init__ for manual re-initialization.

Understand the failure class

Related errors


AI-assisted analysis of huggingface/smolagents@30bb116109 (2026-08-28). Data as JSON: /api/errors/58d4370084d59a54. Report an issue: GitHub.