deepset-ai/haystack · warning · Warning

Mutating attribute '{name}' on an instance of '{type(self)._

Error message

Mutating attribute '{name}' on an instance of '{type(self).__name__}' can lead to unexpected behavior by affecting other parts of the pipeline that use the same dataclass instance. Use `dataclasses.replace(instance, {name}=new_value)` instead. See https://docs.haystack.deepset.ai/docs/custom-components#requirements for details.

What it means

Haystack emits this warning from haystack/utils/dataclasses.py:41 when you assign to a dataclass field on an instance that is shared across pipeline components. Mutating the instance affects every pipeline part holding a reference to the same object, causing hard-to-trace bugs; Haystack recommends creating a new instance with dataclasses.replace().

Source

Thrown at haystack/utils/dataclasses.py:41

    @wraps(original_init)
    def __init_track__(self: T, *args: Any, **kwargs: Any) -> None:
        # We don't raise warnings during initialization, i.e. during the first call to __init__ and __post_init__.
        initializing.add(id(self))
        try:
            return original_init(self, *args, **kwargs)
        finally:
            initializing.discard(id(self))

    @wraps(original_setattr)
    def __setattr_warn__(self: T, name: str, value: Any) -> None:
        # We raise warnings if the dataclass is mutated in-place after initialization.
        if (
            id(self) not in initializing
            and name in getattr(self, "__dataclass_fields__", {})
            and name in getattr(self, "__dict__", {})
        ):
            # We raise a warning if the attribute is a dataclass field and a dictionary key.
            warnings.warn(
                f"Mutating attribute '{name}' on an instance of "
                f"'{type(self).__name__}' can lead to unexpected behavior by affecting other parts of the pipeline "
                "that use the same dataclass instance. "
                f"Use `dataclasses.replace(instance, {name}=new_value)` instead. "
                "See https://docs.haystack.deepset.ai/docs/custom-components#requirements for details.",
                Warning,
                stacklevel=2,
            )
        # mypy infers original_setattr as bound to the type, expecting (str, Any), we call the unbound form
        return original_setattr(self, name, value)  # type: ignore[call-arg, arg-type]

    # mypy considers direct dunder access on a class unsound, ruff prefers direct access
    cls.__init__ = __init_track__  # type: ignore[misc]
    # mypy does not allow assigning to a method, ruff prefers direct access
    cls.__setattr__ = __setattr_warn__  # type: ignore[method-assign, assignment]
    return cls

View on GitHub (pinned to e318778c9b)

Solutions

  1. Replace in-place mutation with dataclasses.replace(instance, field_name=new_value) to create a new instance
  2. Copy the instance first (copy.deepcopy or dataclasses.replace with no changes) before mutating
  3. Restructure pipeline so shared dataclass instances are treated as immutable; pass new instances downstream
  4. Suppress via warnings.filterwarnings('ignore', category=Warning, module='haystack') only after confirming no other pipeline component shares the instance

Example fix

# before
message.text = message.text + " (edited)"  # affects all holders of `message`
pipeline.run(data={...})
# after
edited = dataclasses.replace(message, text=message.text + " (edited)")
pipeline.run(data={"branch": edited})
Defensive patterns

Strategy: fallback

Validate before calling

import dataclasses

def assert_mutation_safe(instance, name: str) -> None:
    fields = getattr(instance, "__dataclass_fields__", {})
    if name in fields:
        raise ValueError(
            f"'{name}' is a shared dataclass field; use dataclasses.replace() instead of assignment"
        )

Type guard

import dataclasses

def is_shared_dataclass_field(instance, name: str) -> bool:
    return (
        name in getattr(instance, "__dataclass_fields__", {})
        and name in getattr(instance, "__dict__", {})
    )

Try / catch

import warnings

with warnings.catch_warnings(record=True) as caught:
    warnings.simplefilter("always")
    obj.field = new_value
    if any("Mutating attribute" in str(w.message) for w in caught):
        obj = dataclasses.replace(obj, field=new_value)  # fallback: create a new instance

Prevention

When it happens

Trigger: Attribute assignment like obj.field = new_value on a dataclass instance decorated with Haystack's mutation-warning mechanism, where name is both a __dataclass_fields__ entry and present in the instance __dict__, outside initialization (id(self) not tracked in the initializing set).

Common situations: Modifying a shared ChatMessage, generated response object, or component output in-place before passing it downstream; caching pipeline results and tweaking fields; two branches of a pipeline accidentally mutating the same dataclass instance.

Related errors


AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30). Data as JSON: /api/errors/455c6e913191b747. Report an issue: GitHub.