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
- Replace in-place mutation with dataclasses.replace(instance, field_name=new_value) to create a new instance
- Copy the instance first (copy.deepcopy or dataclasses.replace with no changes) before mutating
- Restructure pipeline so shared dataclass instances are treated as immutable; pass new instances downstream
- 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
- Treat pipeline dataclass instances (messages, results) as immutable
- Always use dataclasses.replace(instance, field=...) instead of attribute assignment
- Deep-copy an instance before mutating it if you must modify it
- Enable -W error in tests so accidental in-place mutation of shared fields fails immediately
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
- MarkdownHeaderSplitter only works with text documents but co
- Error while unmarshalling serialized pipeline data. This is
- Component instance cannot be added to the pipeline more than
- A component named '{name}' already exists in this pipeline:
- '_debug' is a reserved name for debug output. Choose another
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/455c6e913191b747.
Report an issue: GitHub.