{"record":{"id":"66822b33d64f6e81","repo":"microsoft/autogen","slug":"handoff-name-must-be-a-valid-identifier-values","errorCode":null,"errorMessage":"Handoff name must be a valid identifier: {values['name']}","messagePattern":"Handoff name must be a valid identifier: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/packages/autogen-agentchat/src/autogen_agentchat/base/_handoff.py","lineNumber":43,"sourceCode":"    message: str = Field(default=\"\")\n    \"\"\"The message to the target agent.\n    By default, it will be the result for the handoff tool.\n    If not provided, it is generated from the target agent's name.\"\"\"\n\n    @model_validator(mode=\"before\")\n    @classmethod\n    def set_defaults(cls, values: Dict[str, Any]) -> Dict[str, Any]:\n        if not values.get(\"description\"):\n            values[\"description\"] = f\"Handoff to {values['target']}.\"\n        if not values.get(\"name\"):\n            values[\"name\"] = f\"transfer_to_{values['target']}\".lower()\n        else:\n            name = values[\"name\"]\n            if not isinstance(name, str):\n                raise ValueError(f\"Handoff name must be a string: {values['name']}\")\n            # Check if name is a valid identifier.\n            if not name.isidentifier():\n                raise ValueError(f\"Handoff name must be a valid identifier: {values['name']}\")\n        if not values.get(\"message\"):\n            values[\"message\"] = (\n                f\"Transferred to {values['target']}, adopting the role of {values['target']} immediately.\"\n            )\n        return values\n\n    @property\n    def handoff_tool(self) -> BaseTool[BaseModel, BaseModel]:\n        \"\"\"Create a handoff tool from this handoff configuration.\"\"\"\n\n        def _handoff_tool() -> str:\n            return self.message\n\n        return FunctionTool(_handoff_tool, name=self.name, description=self.description, strict=True)\n\n    \"\"\"\n    The tool that can be used to handoff to the target agent.\n    Typically, the results of the tool's execution are provided to the target agent.","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/microsoft/autogen/blob/027ecf0a379bcc1d09956d46d12d44a3ad9cee14/python/packages/autogen-agentchat/src/autogen_agentchat/base/_handoff.py#L25-L61","documentation":"After confirming the supplied handoff name is a string, the same model_validator requires it to be a valid Python identifier (name.isidentifier()). This is because the name becomes the schema name of the handoff tool exposed to the LLM and must round-trip as a function/tool name. Non-identifier strings (hyphens, spaces, leading digits, dots) are rejected.","triggerScenarios":"Handoff(target='writer', name='handoff-to-writer') (hyphen); name='1st_agent' (leading digit); name='writer.agent' (dot); auto-generated names derived from targets containing spaces or unicode that is not identifier-safe (e.g. target='writing agent' produces 'transfer_to_writing agent'.lower()).","commonSituations":"Agent display names with spaces or hyphens being reused as handoff names; converting tool names from other frameworks (MCP, OpenAPI operationIds) that allow dashes; non-ASCII target names producing identifiers valid in Python but rejected downstream by some model providers.","solutions":["Omit name and let it default, ensuring target itself is identifier-safe","Normalize the name: re.sub(r'\\W|^(?=\\d)', '_', name) to replace invalid characters with underscores","Give agents identifier-friendly names (snake_case) at construction time so derived handoff names are valid"],"exampleFix":"# before\nHandoff(target=\"code reviewer\", name=\"code-reviewer\")\n\n# after\nimport re\nname = re.sub(r'\\W', '_', \"code-reviewer\")  # 'code_reviewer'\nHandoff(target=\"code reviewer\", name=name)","handlingStrategy":"validation","validationCode":"def is_valid_handoff_name(name: str) -> bool:\n    return isinstance(name, str) and name.isidentifier()","typeGuard":"def is_identifier_handoff_name(value) -> bool:\n    \"\"\"Narrow to names Handoff accepts: non-empty str and valid Python identifier.\"\"\"\n    return isinstance(value, str) and value.isidentifier()","tryCatchPattern":"import re\nfrom autogen_agentchat.base import Handoff\n\ntry:\n    h = Handoff(target=\"writer\", name=name)\nexcept ValueError as e:\n    if \"valid identifier\" in str(e):\n        h = Handoff(target=\"writer\", name=re.sub(r'\\W|^(?=\\d)', '_', name))\n    else:\n        raise","preventionTips":["Use snake_case handoff and agent names","Sanitize externally sourced names with re.sub(r'\\W', '_', name)","Pre-validate names with str.isidentifier() in config loaders"],"tags":["handoff","identifier","tool-name","validation","agentchat"],"backgroundTag":null,"analyzedSha":"027ecf0a379bcc1d09956d46d12d44a3ad9cee14","analyzedAt":"2026-08-15T03:38:00.719Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}