{"record":{"id":"e7cf64101fadb448","repo":"python/cpython","slug":"taskgroup-self-r-has-already-been-entered","errorCode":null,"errorMessage":"TaskGroup {self!r} has already been entered","messagePattern":"TaskGroup (.+?) has already been entered","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"Lib/asyncio/taskgroups.py","lineNumber":58,"sourceCode":"        self._cancel_on_enter = False\n\n    def __repr__(self):\n        info = ['']\n        if self._tasks:\n            info.append(f'tasks={len(self._tasks)}')\n        if self._errors:\n            info.append(f'errors={len(self._errors)}')\n        if self._aborting:\n            info.append('cancelling')\n        elif self._entered:\n            info.append('entered')\n\n        info_str = ' '.join(info)\n        return f'<TaskGroup{info_str}>'\n\n    async def __aenter__(self):\n        if self._entered:\n            raise RuntimeError(\n                f\"TaskGroup {self!r} has already been entered\")\n        if self._loop is None:\n            self._loop = events.get_running_loop()\n        self._parent_task = tasks.current_task(self._loop)\n        if self._parent_task is None:\n            raise RuntimeError(\n                f'TaskGroup {self!r} cannot determine the parent task')\n        self._entered = True\n        if self._cancel_on_enter:\n            self.cancel()\n\n        return self\n\n    async def __aexit__(self, et, exc, tb):\n        tb = None\n        try:\n            return await self._aexit(et, exc)\n        finally:","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/asyncio/taskgroups.py#L40-L76","documentation":"RuntimeError raised by TaskGroup.__aenter__ when the same TaskGroup instance is entered a second time (_entered already true). A TaskGroup is a one-shot async context manager tied to a single parent task and lifecycle; it cannot be reused for a second batch of work.","triggerScenarios":"Storing a TaskGroup as an instance attribute and using 'async with self.tg:' in a method called twice; entering the same group in two places (nested or sequential); re-entering after an exception aborted the first run.","commonSituations":"Framework/service classes that create one TaskGroup in __init__ and re-enter per request; retry wrappers that re-run an 'async with tg' block; refactoring a per-call group into a shared field for convenience.","solutions":["Construct a fresh TaskGroup per use: 'async with asyncio.TaskGroup() as tg:' inside the function that runs each batch","If you need a long-lived spawn point, use a dedicated worker task plus an asyncio.Queue instead of a persistent TaskGroup","Never share a TaskGroup across concurrent 'async with' blocks"],"exampleFix":"// before\nclass Runner:\n    def __init__(self):\n        self.tg = asyncio.TaskGroup()\n    async def run(self, fn):\n        async with self.tg:            # second call -> RuntimeError\n            self.tg.create_task(fn())\n\n// after\nclass Runner:\n    async def run(self, fn):\n        async with asyncio.TaskGroup() as tg:\n            tg.create_task(fn())","handlingStrategy":"validation","validationCode":"null","typeGuard":"null","tryCatchPattern":"null","preventionTips":["Always write 'async with asyncio.TaskGroup() as tg:' at the scope that owns one batch — never store and re-enter","If a class needs repeated fan-out, construct the group inside the method","For long-lived spawn points use a worker task plus a queue, not a persistent group"],"tags":["asyncio","taskgroup","api-misuse","structured-concurrency"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}