{"record":{"id":"cd8687c4709cccc9","repo":"microsoft/semantic-kernel","slug":"the-invocation-has-already-been-completed","errorCode":null,"errorMessage":"The invocation has already been completed.","messagePattern":"The invocation has already been completed\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"warning","filePath":"python/semantic_kernel/agents/orchestration/orchestration_base.py","lineNumber":78,"sourceCode":"\n        if self.value is None:\n            if self.cancellation_token.is_cancelled():\n                raise RuntimeError(\"The invocation was canceled before it could complete.\")\n            if self.exception is not None:\n                raise self.exception\n            raise RuntimeError(\"The invocation did not produce a result.\")\n        return self.value\n\n    def cancel(self) -> None:\n        \"\"\"Cancel the invocation.\n\n        This method will cancel the invocation.\n        Actors that have received messages will continue to process them, but no new messages will be processed.\n        \"\"\"\n        if self.cancellation_token.is_cancelled():\n            raise RuntimeError(\"The invocation has already been canceled.\")\n        if self.event.is_set():\n            raise RuntimeError(\"The invocation has already been completed.\")\n\n        self.cancellation_token.cancel()\n        self.event.set()\n\n\n@experimental\nclass OrchestrationBase(ABC, Generic[TIn, TOut]):\n    \"\"\"Base class for multi-agent orchestration.\"\"\"\n\n    t_in: type[TIn] | None = None\n    t_out: type[TOut] | None = None\n\n    def __init__(\n        self,\n        members: list[Agent],\n        name: str | None = None,\n        description: str | None = None,\n        input_transform: Callable[[TIn], Awaitable[DefaultTypeAlias] | DefaultTypeAlias] | None = None,","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/microsoft/semantic-kernel/blob/c028a0c7dc4f0814cdcbaba9d998f187a41197bf/python/semantic_kernel/agents/orchestration/orchestration_base.py#L60-L96","documentation":"OrchestrationResult.cancel() refuses to cancel after the result event is already set, i.e. the invocation has already completed (success, failure, or prior cancel). Cancel-after-complete is a no-op at best and a logic error, so it raises.","triggerScenarios":"Calling `result.cancel()` after the orchestration already finished and set the event — e.g. after `await result.get()` returned, or after a prior cancel()/completion.","commonSituations":"A finally block that cancels unconditionally even on the success path. A timeout handler firing after the run already completed. Cancel wired into both success and error cleanup.","solutions":["Only cancel when the run is still in flight: check `if not result.event.is_set(): result.cancel()`.","Move cancellation out of the success/completion path; only cancel on actual abort/timeout.","Await get() with a timeout and cancel only if get() raised TimeoutError."],"exampleFix":"// before\nvalue = await result.get()\nresult.cancel()  # raises 'already been completed'\n\n// after\ntry:\n    value = await result.get(timeout=30)\nexcept asyncio.TimeoutError:\n    if not result.event.is_set():\n        result.cancel()","handlingStrategy":"validation","validationCode":"# Only cancel while the run is still in flight\nif not result.event.is_set():\n    result.cancel()","typeGuard":"def result_still_running(result) -> bool:\n    return not result.event.is_set()","tryCatchPattern":null,"preventionTips":["Don't cancel on the success path; only on actual abort/timeout.","Use get(timeout=...) and cancel only if it times out.","Guard cancel() with `if not result.event.is_set()`."],"tags":["semantic-kernel","orchestration","cancellation","result","lifecycle"],"backgroundTag":null,"analyzedSha":"c028a0c7dc4f0814cdcbaba9d998f187a41197bf","analyzedAt":"2026-08-13T13:48:05.040Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}