PrefectHQ/fastmcp · error
Tool {func_name!r}: timeout cannot be enforced when run_in_t
Error message
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. What it means
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.
Source
Thrown at fastmcp_slim/fastmcp/tools/function_tool.py:315
parsed_fn = ParsedFunction.from_function(fn)
func_name = metadata.name or parsed_fn.name
if func_name == "<lambda>":
raise ValueError("You must provide a name for lambda functions")
# Inline sync execution has no cancellation checkpoints, so
# anyio.fail_after cannot preempt the call — the timeout would be
# silently ignored. Reject the combination so users make an
# explicit choice. Async generators are async even though
# is_coroutine_function returns False for them; the generator's
# iteration has checkpoints, so timeout enforcement still works.
if (
metadata.timeout is not None
and not metadata.run_in_thread
and not is_coroutine_function(fn)
and not inspect.isasyncgenfunction(fn)
):
raise ValueError(
f"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."
)
# Normalize task to TaskConfig
task_value = metadata.task
if task_value is None:
task_config = TaskConfig(mode="forbidden")
elif isinstance(task_value, bool):
task_config = TaskConfig.from_bool(task_value)
else:
task_config = task_value
task_config.validate_function(fn, func_name)
# Handle output_schemaView on GitHub (pinned to 1f02114297)
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
Example fix
// before FunctionTool.from_function(sync_fn, timeout=10.0, run_in_thread=False) // after FunctionTool.from_function(sync_fn, timeout=10.0) # default run_in_thread=True
Defensive patterns
Strategy: validation
Validate before calling
import inspect, asyncio
def check_timeout_viable(fn, timeout=None, run_in_thread=True):
if timeout is not None and not run_in_thread and not inspect.iscoroutinefunction(fn) and not inspect.isasyncgenfunction(fn):
raise ValueError('timeout unenforceable: enable run_in_thread or drop timeout') Type guard
def timeout_would_be_noop(fn, run_in_thread: bool) -> bool:
return (not run_in_thread
and not inspect.iscoroutinefunction(fn)
and not inspect.isasyncgenfunction(fn)) Try / catch
try:
tool = FunctionTool.from_function(fn, timeout=t, run_in_thread=False)
except ValueError as e:
if 'timeout cannot be enforced' in str(e):
tool = FunctionTool.from_function(fn, timeout=t) # allow thread dispatch Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Failed to initialize server session
- -32000
- Invalid timeout type: {type(value)}
- module {__name__!r} has no attribute {name!r}
- Cannot resolve tool reference: {fn!r}
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/5697292738039fc4.
Report an issue: GitHub.