From 2727b8cd0112ab778edb2a9f5ad0e6cd5d0824fc Mon Sep 17 00:00:00 2001 From: radu-mocanu Date: Wed, 22 Jul 2026 15:17:25 +0300 Subject: [PATCH] feat: instrument agent run startup for hang diagnosis --- packages/uipath/pyproject.toml | 2 +- packages/uipath/src/uipath/_cli/__init__.py | 25 +++++++++++++++++++++ packages/uipath/src/uipath/_cli/cli_run.py | 14 ++++++++++++ packages/uipath/uv.lock | 2 +- 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index 2e5404478..ec813ed17 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath" -version = "2.13.13" +version = "2.13.14" description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath/src/uipath/_cli/__init__.py b/packages/uipath/src/uipath/_cli/__init__.py index f12d46560..b2bb65da3 100644 --- a/packages/uipath/src/uipath/_cli/__init__.py +++ b/packages/uipath/src/uipath/_cli/__init__.py @@ -1,3 +1,4 @@ +import faulthandler import importlib.metadata import os import sys @@ -9,6 +10,30 @@ from uipath._utils._logs import setup_logging from uipath.platform.constants import DOTENV_FILE + +def _arm_startup_traceback_watchdog() -> None: + """Dump all thread stacks to stderr if startup blocks past a threshold. + + Armed at import time (before the lazy command modules load) so a hang + in module import or runtime construction is captured in the job logs, + where a stalled process is otherwise silent. Cancelled by the run + command once the runtime is ready, so a healthy run never dumps. + Opt-in via UIPATH_STARTUP_TRACEBACK_SECONDS; auto-enabled in the + serverless runtime with a conservative default. + """ + raw = os.environ.get("UIPATH_STARTUP_TRACEBACK_SECONDS") + if raw is None and os.environ.get("UIPATH_ENVIRONMENT_TAG") == "Serverless": + raw = "120" + try: + seconds = int(raw) if raw else 0 + except ValueError: + seconds = 0 + if seconds > 0: + faulthandler.dump_traceback_later(seconds, repeat=True, file=sys.stderr) + + +_arm_startup_traceback_watchdog() + # Windows console uses codepages (e.g. cp1252) that can't encode Unicode # characters used by Rich spinners (Braille) and emoji output. # When piped (not a TTY), keep the system encoding so the parent process diff --git a/packages/uipath/src/uipath/_cli/cli_run.py b/packages/uipath/src/uipath/_cli/cli_run.py index 9d12a86c3..5b2b15a95 100644 --- a/packages/uipath/src/uipath/_cli/cli_run.py +++ b/packages/uipath/src/uipath/_cli/cli_run.py @@ -1,4 +1,6 @@ import asyncio +import faulthandler +import logging from typing import Any import click @@ -41,6 +43,7 @@ from .middlewares import Middlewares console = ConsoleLogger() +logger = logging.getLogger(__name__) class _RunDiscoveryError(EntrypointDiscoveryException): @@ -222,16 +225,19 @@ async def execute() -> None: factory: UiPathRuntimeFactoryProtocol | None = None governance_bootstrap: GovernanceBootstrap | None = None try: + logger.info("[startup] resolving runtime factory") factory = UiPathRuntimeFactoryRegistry.get(context=ctx) resolved_entrypoint = entrypoint if not resolved_entrypoint: + logger.info("[startup] discovering entrypoint") available = factory.discover_entrypoints() if len(available) == 1: resolved_entrypoint = available[0] else: raise _RunDiscoveryError(available) + logger.info("[startup] loading factory settings") factory_settings = await factory.get_settings() trace_settings = ( factory_settings.trace_settings @@ -248,6 +254,7 @@ async def execute() -> None: if factory_settings else None ) + logger.info("[startup] resolving governance") governance_bootstrap = await resolve_governance( agent_framework=agent_framework, agent_type=agent_type, @@ -262,11 +269,18 @@ async def execute() -> None: governance_bootstrap.evaluator ) + logger.info( + "[startup] building runtime and compiling graph " + "(entrypoint=%s)", + resolved_entrypoint, + ) base_runtime = await factory.new_runtime( resolved_entrypoint, governance_runtime_id, **new_runtime_kwargs, ) + logger.info("[startup] runtime ready, starting execution") + faulthandler.cancel_dump_traceback_later() if governance_bootstrap is not None: base_runtime = governance_bootstrap.wrap_runtime( diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index 0f554676b..3219ee07a 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2598,7 +2598,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.13.13" +version = "2.13.14" source = { editable = "." } dependencies = [ { name = "applicationinsights" },