Sockets.__getattribute__ references _sockets but the actual attribute is _sockets_dict — the optimization at lines 127-132 is dead code due to a typo
Maintainers usually reply within 1 day
@julian-risch is already working on this.
Since Sep 25, 2026.
Assessment
- Difficulty
- 1/5
- Estimated time
- Under an hour
- Newbie friendliness
- 91/100
Research direction
Open haystack/core/component/sockets.py and inspect init alongside getattribute. Change the attribute name used by the lookup to match _sockets_dict, then run hatch -e default run pytest test/core -q. Done means _sockets is no longer referenced and the existing socket accessors continue producing their current results.
Written by the indexing model from the issue text.
Description
Sockets.__getattribute__ references _sockets but the attribute is actually _sockets_dict — the optimization is dead code
Problem
haystack/core/component/sockets.py:126-134 defines a custom __getattribute__ that looks up the name in self._sockets — but the actual instance attribute is named self._sockets_dict (set in __init__ at line 77). The try block at lines 127-132 always raises AttributeError and falls through to object.__getattribute__(self, name) at line 134.
The class still works because __init__ does self.__dict__.update(sockets_dict) at line 78, which exposes each socket as a regular instance attribute. The custom __getattribute__ looks like an intended optimization (skip default attribute machinery and look up directly in the dict) but it never fires.
Evidence (against current main 43547276ad)
# haystack/core/component/sockets.py, lines 53-78
def __init__(
self,
component: "Component", # type: ignore[name-defined] # noqa: F821
sockets_dict: SocketsDict,
sockets_io_type: SocketsIOType,
) -> None:
...
self._sockets_io_type = sockets_io_type # line 75
self._component = component # line 76
self._sockets_dict = sockets_dict # line 77
self.__dict__.update(sockets_dict) # line 78
# haystack/core/component/sockets.py, lines 126-134
def __getattribute__(self, name: Any) -> Any:
try:
sockets = object.__getattribute__(self, "_sockets") # line 128
if name in sockets:
return sockets[name]
except AttributeError:
pass
return object.__getattribute__(self, name)
_sockets is never set anywhere in the file. _sockets_dict is set at line 77 and used at lines 87, 97, 101, 114, 143.
Verified locally against current main (43547276ad) via hatch -e default run python:
from typing import Any
from haystack.core.component.sockets import Sockets
from haystack.core.component.types import InputSocket
class FakeComponent:
pass
sockets_dict = {
"question": InputSocket("question", Any),
"documents": InputSocket("documents", Any),
}
s = Sockets(component=FakeComponent(), sockets_dict=sockets_dict, sockets_io_type=InputSocket)
>>> hasattr(s, "_sockets")
False
>>> hasattr(s, "_sockets_dict")
True
>>> s.question
InputSocket(name='question', type=typing.Any, default_value=<class '_empty'>, is_lazy_variadic=False, is_greedy=False, senders=[], wrap_input_in_list=True)
>>> s.documents
InputSocket(...)
>>> object.__getattribute__(s, '_sockets')
AttributeError: 'Sockets' object has no attribute '_sockets'
So every call to __getattribute__ raises AttributeError on line 128 and falls through to object.__getattribute__ on line 134. The optimization looks like the intended hot-path (skip default attribute machinery and look up directly in the dict) but it never runs.
Why it matters
The bug is two-fold:
-
Latent correctness issue. Any future change that removes
self.__dict__.update(sockets_dict)at line 78 (e.g., to fix a different issue) would break the class immediately, because the__getattribute__doesn't actually fall back to the dict. The current__dict__.updateis the only reasons.questionworks. There is no test or comment explaining why__getattribute__exists. -
Performance issue (minor). Every socket attribute access incurs the failed try/except + the default attribute lookup. For pipelines that introspect sockets (e.g., for type checking in
Pipeline.run_async), this is a small overhead per call. -
Documentation hygiene. The custom
__getattribute__is a misleading piece of code. Future maintainers reading this will assume it works and may add more logic on top of it without realizing the try block is dead.
Proposed fix
One-character fix in __getattribute__ on line 128:
# Before
sockets = object.__getattribute__(self, "_sockets")
# After
sockets = object.__getattribute__(self, "_sockets_dict")
That's the entire fix. Two alternatives considered below.
Alternatives considered
- Remove
__getattribute__entirely, rely onself.__dict__.update(sockets_dict)at line 78. The class already supports attribute access via__dict__.update; the custom method is redundant once we acknowledge the actual behavior. Cleaner long-term, but a slightly larger diff (delete 9 lines). Rejected as out of scope for a minimal cycle-36 fix; can be a follow-up. - Make
__getattribute__symmetric with the rest of the API by also reading_sockets_dictin__setitem__(line 97),__contains__(line 101),get(line 114), and__repr__(line 143). The other accessors already use_sockets_dictcorrectly — only__getattribute__is broken. Rejected; the other accessors are already correct.
Scope
- One file:
haystack/core/component/sockets.py - One line of code changed (line 128)
- One line of the docstring at line 126 unchanged (the function-level docstring isn't there for this method)
- No public API change, no behavior change, no test change
Backward compatibility
None. The fix makes __getattribute__ actually run the optimization it appears to be doing. External callers see no difference; sockets["foo"], sockets.get("foo"), sockets.foo, "foo" in sockets, sockets == sockets2 all continue to work.
Acceptance criteria
self._socketsis no longer referenced insockets.pyhasattr(s, "_sockets")becomesTrueafter construction (because__getattribute__now reads_sockets_dict)s.question,s.documents,sockets.__contains__("question"),sockets.get("question"),sockets.__repr__()all continue to produce the same results as beforehatch -e default run pytest test/core -q(or equivalent) passes without modification
Risks
None. The fix is one-character; the function's behavior with the fix matches what the surrounding __dict__.update already provides.
- Dominant language
- Python
- Stars
- 26.6k
- Forks
- 3.2k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 263
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from deepset-ai/haystack
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
deepset-ai/haystack#13029 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
deepset-ai/haystack#13022 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
deepset-ai/haystack#12994 · 1 assignee ·
Maintainers usually reply within 1 day
-
MarkdownHeaderSplitter treats headings inside longer closing fences as headersPossibly taken @julian-risch claimed this 2 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
deepset-ai/haystack#12954 · 2 comments · 1 assignee ·
Maintainers usually reply within 1 day
-
P3
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
deepset-ai/haystack#12945 ·
Maintainers usually reply within 1 day
All issues in deepset-ai/haystack
Similar issues
-
namespace operations
Difficulty 1/5 Under an hour Newbie friendliness 82/100
EclipseFdn/open-vsx.org#13573 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
collective/icalendar#1854 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
rancher/rancher-ai-agent#412 ·
Maintainers usually reply within 6 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
TUDelftGeodesy/DePSI#134 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
HenriquesLab/rxiv-maker#335 ·