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

  1. Only call remove(pred, succ) for edges previously created with add(pred, succ).
  2. Guard with: if sequencer.is_step(succ) and succ in sequencer._preds.
  3. 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

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


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/3fd7a17cb0088809. Report an issue: GitHub.