pypa/pip · error · ValueError
%r not a successor of anything
Error message
%r not a successor of anything
What it means
Raised as ValueError by Sequencer.remove (marked pragma: no cover, i.e. defensive) when the successor node has no predecessors recorded at all — self._preds[succ] raises KeyError. This indicates an internal inconsistency: trying to remove an edge whose successor was never added via add()/add_edge. In normal use add() always populates both _preds and _succs, so this fires only on out-of-order or double-removed edges.
Source
Thrown at src/pip/_vendor/distlib/util.py:1121
for k, v in list(self._preds.items()):
if not v:
del self._preds[k]
for k, v in list(self._succs.items()):
if not v:
del self._succs[k]
def add(self, pred, succ):
assert pred != succ
self._preds.setdefault(succ, set()).add(pred)
self._succs.setdefault(pred, set()).add(succ)
def remove(self, pred, succ):
assert pred != succ
try:
preds = self._preds[succ]
succs = self._succs[pred]
except KeyError: # pragma: no cover
raise ValueError('%r not a successor of anything' % succ)
try:
preds.remove(pred)
succs.remove(succ)
except KeyError: # pragma: no cover
raise ValueError('%r not a successor of %r' % (succ, pred))
def is_step(self, step):
return (step in self._preds or step in self._succs or step in self._nodes)
def get_steps(self, final):
if not self.is_step(final):
raise ValueError('Unknown: %r' % final)
result = []
todo = []
seen = set()
todo.append(final)
while todo:
step = todo.pop(0)View on GitHub (pinned to f399c37189)
Solutions
- Only call remove(pred, succ) for edges previously created with add(pred, succ).
- Guard with: if sequencer.is_step(succ) and succ in sequencer._preds.
- Track created edges in your own set and remove only from that set.
Example fix
# before
seq.remove('A', 'B')
# after
if 'B' in seq._preds:
seq.remove('A', 'B') Defensive patterns
Strategy: validation
Validate before calling
def safe_remove_edge(seq, pred, succ):
if succ in seq._preds and pred in seq._preds[succ]:
seq.remove(pred, succ) Type guard
def edge_may_exist(seq, pred, succ) -> bool:
return succ in seq._preds Try / catch
try:
seq.remove(pred, succ)
except ValueError as e:
if 'not a successor of anything' in str(e):
pass Prevention
- Only remove edges you created with add().
- Use remove_node(node, edges=True) for bulk teardown.
When it happens
Trigger: Calling Sequencer().remove('A', 'B') without first calling add('A', 'B'); removing an edge after remove_node(node, edges=True) already cleared the predecessor map; calling remove with a successor that was never introduced to the sequencer.
Common situations: Lifecycle bugs where a dependency edge is removed twice; incorrect ordering of add/remove in graph-building code; cleanup routines that remove edges speculatively.
Related errors
- %r not a successor of %r
- Unknown: %r
- No subscribers: %r
- invalid requirement: %s
- unexpected trailing data: %s
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/3fd7a17cb0088809.
Report an issue: GitHub.