{"record":{"id":"c265cd109a8e8edc","repo":"pypa/pip","slug":"r-not-a-successor-of-r","errorCode":null,"errorMessage":"%r not a successor of %r","messagePattern":"%r not a successor of %r","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/distlib/util.py","lineNumber":1126,"sourceCode":"                    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)\n            if step in seen:\n                # if a step was already seen,\n                # move it to the end (so it will appear earlier\n                # when reversed on return) ... but not for the\n                # final step, as that would be confusing for","sourceCodeStart":1108,"sourceCodeEnd":1144,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_vendor/distlib/util.py#L1108-L1144","documentation":"Raised as ValueError by Sequencer.remove (pragma: no cover) when the successor's predecessor set exists but does not contain the specific pred, OR the predecessor's successor set does not contain succ — i.e. the exact (pred, succ) edge was never added or was already removed. It is the per-edge analogue of error 233, catching a missing specific relationship rather than a wholly-unknown node.","triggerScenarios":"Calling remove('A','B') after the edge was already removed; remove('A','B') when only add('A','C') and add('X','B') exist (no A->B edge); double-cleanup of the same dependency.","commonSituations":"Idempotent teardown code that calls remove multiple times; rebuild logic that re-removes edges from a previous run; race between two cleanup paths.","solutions":["Make removal idempotent: check succ in seq._succs.get(pred, ()) before removing.","Maintain an edges set and remove from it, only calling Sequencer.remove when present.","Wrap remove in try/except ValueError for best-effort cleanup."],"exampleFix":"# before\nseq.remove('A', 'B')\n# after\nif 'B' in seq._succs.get('A', ()):\n    seq.remove('A', 'B')","handlingStrategy":"validation","validationCode":"def safe_remove_edge(seq, pred, succ):\n    if pred in seq._succs and succ in seq._succs[pred]:\n        seq.remove(pred, succ)","typeGuard":"def edge_exists(seq, pred, succ) -> bool:\n    return pred in seq._succs and succ in seq._succs.get(pred, ())","tryCatchPattern":"try:\n    seq.remove(pred, succ)\nexcept ValueError as e:\n    if 'not a successor of' in str(e):\n        pass","preventionTips":["Make removal idempotent.","Track edges in your own set."],"tags":["sequencer","graph","distlib","dependency-ordering","idempotency"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}