{"record":{"id":"4b8112434d1fa573","repo":"python/cpython","slug":"raw-stream-already-detached","errorCode":null,"errorMessage":"raw stream already detached","messagePattern":"raw stream already detached","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_pyio.py","lineNumber":823,"sourceCode":"\n    ### Flush and close ###\n\n    def flush(self):\n        if self.closed:\n            raise ValueError(\"flush on closed file\")\n        self.raw.flush()\n\n    def close(self):\n        if self.raw is not None and not self.closed:\n            try:\n                # may raise BlockingIOError or BrokenPipeError etc\n                self.flush()\n            finally:\n                self.raw.close()\n\n    def detach(self):\n        if self.raw is None:\n            raise ValueError(\"raw stream already detached\")\n        self.flush()\n        raw = self._raw\n        self._raw = None\n        return raw\n\n    ### Inquiries ###\n\n    def seekable(self):\n        return self.raw.seekable()\n\n    @property\n    def raw(self):\n        return self._raw\n\n    @property\n    def closed(self):\n        return self.raw.closed\n","sourceCodeStart":805,"sourceCodeEnd":841,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pyio.py#L805-L841","documentation":"BufferedRaw.detach (Lib/_pyio.py:823) raises ValueError when called on a buffered stream whose underlying raw object has already been detached (self.raw is None). detach() disassociates the raw stream and returns it for independent use; afterwards the buffered wrapper is unusable, and a second detach is rejected.","triggerScenarios":"Calling buf.detach() twice; calling read()/write()/flush() after detach() (these access self.raw, which is None, and most paths surface the detach error or AttributeError); detaching in cleanup code that runs again on retry.","commonSituations":"Taking over sys.stdin/sys.stdout buffers via sys.stdout.detach() or io.TextIOWrapper.detach() and then library code also touching or detaching the stream; mixing manual detach with `with` blocks that close the wrapper on exit.","solutions":["Call detach() exactly once and keep the returned raw object as the single owner; stop using the buffered wrapper afterwards.","If taking over a wrapper's buffer (e.g. sys.stdout), ensure no other code (print, logging, atexit, context-manager exit) uses or closes the wrapper afterwards.","Check `buf.raw is not None` (guarded by try/except since raw is a property) or track detachment in your own state before detaching."],"exampleFix":"# before\nraw = wrapper.detach()\nraw2 = wrapper.detach()  # ValueError: raw stream already detached\n\n# after\nraw = wrapper.detach()\ndel wrapper  # wrapper is spent; use raw directly from here on","handlingStrategy":"validation","validationCode":"def detach_once(wrapper, state={'detached': False}):\n    if state['detached']:\n        raise ValueError('raw stream already detached earlier')\n    state['detached'] = True\n    return wrapper.detach()","typeGuard":null,"tryCatchPattern":"try:\n    raw = wrapper.detach()\nexcept ValueError as e:\n    if 'already detached' in str(e):\n        raw = None  # already taken; handle ownership elsewhere\n    else:\n        raise","preventionTips":["Call detach() once and delete the wrapper reference immediately after.","When taking over sys.stdout/sys.stdin buffers, disable or reconfigure print/logging first so nothing touches the wrapper later.","Never wrap a stream you intend to detach in a `with` block — exit's close() will act on a spent wrapper."],"tags":["io","detach","lifecycle","valueerror"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}