{"record":{"id":"5697292738039fc4","repo":"PrefectHQ/fastmcp","slug":"tool-func-name-r-timeout-cannot-be-enforced-whe","errorCode":null,"errorMessage":"Tool {func_name!r}: timeout cannot be enforced when run_in_thread=False on a sync function. Inline execution has no cancellation checkpoints, so the timeout would be a no-op. Either drop the timeout or remove run_in_thread=False and accept worker-thread dispatch.","messagePattern":"Tool (.+?): timeout cannot be enforced when run_in_thread=False on a sync function\\. Inline execution has no cancellation checkpoints, so the timeout would be a no-op\\. Either drop the timeout or remove run_in_thread=False and accept worker-thread dispatch\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/tools/function_tool.py","lineNumber":315,"sourceCode":"        parsed_fn = ParsedFunction.from_function(fn)\n        func_name = metadata.name or parsed_fn.name\n\n        if func_name == \"<lambda>\":\n            raise ValueError(\"You must provide a name for lambda functions\")\n\n        # Inline sync execution has no cancellation checkpoints, so\n        # anyio.fail_after cannot preempt the call — the timeout would be\n        # silently ignored. Reject the combination so users make an\n        # explicit choice. Async generators are async even though\n        # is_coroutine_function returns False for them; the generator's\n        # iteration has checkpoints, so timeout enforcement still works.\n        if (\n            metadata.timeout is not None\n            and not metadata.run_in_thread\n            and not is_coroutine_function(fn)\n            and not inspect.isasyncgenfunction(fn)\n        ):\n            raise ValueError(\n                f\"Tool {func_name!r}: timeout cannot be enforced when \"\n                \"run_in_thread=False on a sync function. Inline execution has \"\n                \"no cancellation checkpoints, so the timeout would be a no-op. \"\n                \"Either drop the timeout or remove run_in_thread=False and \"\n                \"accept worker-thread dispatch.\"\n            )\n\n        # Normalize task to TaskConfig\n        task_value = metadata.task\n        if task_value is None:\n            task_config = TaskConfig(mode=\"forbidden\")\n        elif isinstance(task_value, bool):\n            task_config = TaskConfig.from_bool(task_value)\n        else:\n            task_config = task_value\n        task_config.validate_function(fn, func_name)\n\n        # Handle output_schema","sourceCodeStart":297,"sourceCodeEnd":333,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/tools/function_tool.py#L297-L333","documentation":"For synchronous functions, a timeout is enforced by anyio.fail_after only when the function runs in a worker thread (run_in_thread=True), because inline sync execution blocks the event loop with no cancellation checkpoints. This ValueError rejects combinations where a timeout would be silently ignored.","triggerScenarios":"FunctionTool.from_function(fn, timeout=5.0, run_in_thread=False) where fn is a plain sync function (not a coroutine function and not an async generator function).","commonSituations":"Users explicitly disabling thread dispatch for performance while keeping a timeout configured; defaults inherited from metadata that set timeout without checking run_in_thread; copying a config that assumed async fn.","solutions":["Drop the timeout parameter if the sync function must run inline","Remove run_in_thread=False so the function runs in a worker thread where the timeout is enforced","Make fn async (async def) so cancellation checkpoints exist"],"exampleFix":"// before\nFunctionTool.from_function(sync_fn, timeout=10.0, run_in_thread=False)\n// after\nFunctionTool.from_function(sync_fn, timeout=10.0)  # default run_in_thread=True","handlingStrategy":"validation","validationCode":"import inspect, asyncio\ndef check_timeout_viable(fn, timeout=None, run_in_thread=True):\n    if timeout is not None and not run_in_thread and not inspect.iscoroutinefunction(fn) and not inspect.isasyncgenfunction(fn):\n        raise ValueError('timeout unenforceable: enable run_in_thread or drop timeout')","typeGuard":"def timeout_would_be_noop(fn, run_in_thread: bool) -> bool:\n    return (not run_in_thread\n            and not inspect.iscoroutinefunction(fn)\n            and not inspect.isasyncgenfunction(fn))","tryCatchPattern":"try:\n    tool = FunctionTool.from_function(fn, timeout=t, run_in_thread=False)\nexcept ValueError as e:\n    if 'timeout cannot be enforced' in str(e):\n        tool = FunctionTool.from_function(fn, timeout=t)  # allow thread dispatch","preventionTips":["Only set timeout together with run_in_thread=True for sync functions","Default to run_in_thread=True when timeouts matter","Document in your tool factory that inline sync runs are uncancellable"],"tags":["python","timeout","concurrency","sync-async"],"backgroundTag":"unenforceable-timeout","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}