{"record":{"id":"3fd7a17cb0088809","repo":"pypa/pip","slug":"r-not-a-successor-of-anything","errorCode":null,"errorMessage":"%r not a successor of anything","messagePattern":"%r not a successor of anything","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/distlib/util.py","lineNumber":1121,"sourceCode":"            for k, v in list(self._preds.items()):\n                if not v:\n                    del self._preds[k]\n            for k, v in list(self._succs.items()):\n                if not v:\n                    del self._succs[k]\n\n    def add(self, pred, succ):\n        assert pred != succ\n        self._preds.setdefault(succ, set()).add(pred)\n        self._succs.setdefault(pred, set()).add(succ)\n\n    def remove(self, pred, succ):\n        assert pred != succ\n        try:\n            preds = self._preds[succ]\n            succs = self._succs[pred]\n        except KeyError:  # pragma: no cover\n            raise ValueError('%r not a successor of anything' % succ)\n        try:\n            preds.remove(pred)\n            succs.remove(succ)\n        except KeyError:  # pragma: no cover\n            raise ValueError('%r not a successor of %r' % (succ, pred))\n\n    def is_step(self, step):\n        return (step in self._preds or step in self._succs or step in self._nodes)\n\n    def get_steps(self, final):\n        if not self.is_step(final):\n            raise ValueError('Unknown: %r' % final)\n        result = []\n        todo = []\n        seen = set()\n        todo.append(final)\n        while todo:\n            step = todo.pop(0)","sourceCodeStart":1103,"sourceCodeEnd":1139,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_vendor/distlib/util.py#L1103-L1139","documentation":"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.","triggerScenarios":"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.","commonSituations":"Lifecycle bugs where a dependency edge is removed twice; incorrect ordering of add/remove in graph-building code; cleanup routines that remove edges speculatively.","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."],"exampleFix":"# before\nseq.remove('A', 'B')\n# after\nif 'B' in seq._preds:\n    seq.remove('A', 'B')","handlingStrategy":"validation","validationCode":"def safe_remove_edge(seq, pred, succ):\n    if succ in seq._preds and pred in seq._preds[succ]:\n        seq.remove(pred, succ)","typeGuard":"def edge_may_exist(seq, pred, succ) -> bool:\n    return succ in seq._preds","tryCatchPattern":"try:\n    seq.remove(pred, succ)\nexcept ValueError as e:\n    if 'not a successor of anything' in str(e):\n        pass","preventionTips":["Only remove edges you created with add().","Use remove_node(node, edges=True) for bulk teardown."],"tags":["sequencer","graph","distlib","dependency-ordering","lifecycle"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}