Skip to content

fix: clear stale socket references on neighbors in remove_component#11971

Open
chuenchen309 wants to merge 6 commits into
deepset-ai:mainfrom
chuenchen309:fix/remove-component-stale-sockets
Open

fix: clear stale socket references on neighbors in remove_component#11971
chuenchen309 wants to merge 6 commits into
deepset-ai:mainfrom
chuenchen309:fix/remove-component-stale-sockets

Conversation

@chuenchen309

Copy link
Copy Markdown
Contributor

Problem

Pipeline.remove_component resets only the removed component's own sockets,
but leaves the removed component's name behind on its neighbors' sockets:

  • a downstream component keeps it in its input socket's senders
  • an upstream component keeps it in its output socket's receivers

graph.remove_node drops the edges from the graph, but the senders/receivers
lists live on the socket objects held by the surviving component instances, and
those are never touched.

These dangling references corrupt pipeline introspection and validation:

  • Pipeline.inputs() computes is_mandatory = socket.is_mandatory and not socket.senders. A stale sender makes not socket.senders false, so a
    now-unconnected mandatory input is hidden from inputs().
  • validate_input then raises a spurious "... is already connected to component '<removed>'" error — naming a component that no longer exists — when the user
    tries to feed that input directly.

Reproduction

from haystack.core.pipeline.base import PipelineBase
from haystack.testing.factory import component_class

Producer = component_class("Producer", output_types={"value": int})
Consumer = component_class("Consumer", input_types={"value": int})

p = PipelineBase()
p.add_component("producer", Producer())
p.add_component("consumer", Consumer())
p.connect("producer.value", "consumer.value")

p.remove_component("producer")

sock = p.get_component("consumer").__haystack_input__._sockets_dict["value"]
print(sock.senders)   # ['producer']  -> dangling reference to a deleted component
print(p.inputs())     # {}            -> mandatory 'consumer.value' input is hidden

Fix

Strip the removed component's name from its neighbors' sockets, walking the
incident edges (which still carry the from_socket/to_socket objects) before
remove_node.

Tests

Added two tests in test/core/pipeline/test_pipeline_base.py (pure Python, no
heavy deps) covering the downstream-neighbor senders and upstream-neighbor
receivers cases. Both fail before the change and pass after. The
downstream test also asserts inputs() re-exposes the mandatory input and
validate_input no longer raises.

$ python -m pytest test/core/pipeline/test_pipeline_base.py -k "stale_senders_on_downstream or stale_receivers_on_upstream"
2 passed

ruff check / ruff format --check pass on the changed files. Added a release
note under releasenotes/notes/.


Disclosure: I used AI assistance (Claude) to help locate this stale-state bug
and draft the tests. I reviewed the change, ran the suite, and take
responsibility for its correctness.

`Pipeline.remove_component` reset only the removed component's own sockets, but
left the removed component's name in its neighbors' sockets: a downstream
component kept it in its input socket's `senders`, and an upstream component kept
it in its output socket's `receivers`. `graph.remove_node` drops the edges but
does not touch the socket objects held by the surviving components.

These dangling references corrupt pipeline introspection and validation:
`Pipeline.inputs()` computes `is_mandatory = socket.is_mandatory and not
socket.senders`, so a stale sender hides a now-unconnected mandatory input; and
`validate_input` raises a spurious "already connected to component ..." error
(naming the deleted component) when that input is fed directly.

Strip the removed component's name from its neighbors' sockets, walking the
incident edges before `remove_node` while they still carry the socket objects.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chuenchen309
chuenchen309 requested a review from a team as a code owner July 13, 2026 01:30
@chuenchen309
chuenchen309 requested review from davidsbatista and removed request for a team July 13, 2026 01:30
@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

@chuenchen309 is attempting to deploy a commit to the deepset Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant

CLAassistant commented Jul 13, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

davidsbatista and others added 4 commits July 13, 2026 09:44
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t__ in new tests

Matches the existing # type: ignore[attr-defined] convention already used
elsewhere in this file for the same dunder-attribute access pattern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added the type:documentation Improvements on the docs label Jul 13, 2026
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  haystack/core/pipeline
  base.py
Project Total  

This report was generated by python-coverage-comment-action

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants