From eb0efa26b6267b88ec8a93046fb1822383a0f8d3 Mon Sep 17 00:00:00 2001 From: Sahil Wakode Date: Mon, 19 Oct 2026 16:04:04 +0530 Subject: [PATCH 01/11] feat(tasks): accept task_source_metadata when creating quick form tasks [ACTN-11909] --- .../platform/action_center/_tasks_service.py | 9 +++++++- .../tests/services/test_actions_service.py | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py index 36e2a012e..8a956b4f0 100644 --- a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py +++ b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py @@ -274,6 +274,7 @@ def _create_quickform_spec( is_actionable_message_enabled: Optional[bool] = None, actionable_message_metadata: Optional[Dict[str, Any]] = None, source_name: str = "Agent", + task_source_metadata: Optional[Dict[str, Any]] = None, ) -> RequestSpec: """Build the RequestSpec for Orchestrator's GenericTasks/CreateTask endpoint. @@ -304,7 +305,7 @@ def _create_quickform_spec( ) if actionable_message_metadata is not None: json_payload["actionableMessageMetaData"] = actionable_message_metadata - _apply_task_source(json_payload, source_name) + _apply_task_source(json_payload, source_name, custom_metadata=task_source_metadata) return RequestSpec( method="POST", @@ -704,6 +705,7 @@ async def create_quickform_async( actionable_message_metadata: Optional[Dict[str, Any]] = None, creator_job_key: Optional[str] = None, source_name: str = "Agent", + task_source_metadata: Optional[Dict[str, Any]] = None, ) -> Task: """Creates a new QuickForm task asynchronously. @@ -737,6 +739,8 @@ async def create_quickform_async( creator_job_key: Optional. Identifies the job that triggered the inline schema creation/upsert. source_name: Source name on TaskSource. Defaults to 'Agent'. + task_source_metadata: Optional extra keys merged into + taskSource.taskSourceMetadata, same as in `create_async()`. Returns: Task: The created task object. @@ -754,6 +758,7 @@ async def create_quickform_async( is_actionable_message_enabled=is_actionable_message_enabled, actionable_message_metadata=actionable_message_metadata, source_name=source_name, + task_source_metadata=task_source_metadata, ) response = await self.request_async( @@ -794,6 +799,7 @@ def create_quickform( actionable_message_metadata: Optional[Dict[str, Any]] = None, creator_job_key: Optional[str] = None, source_name: str = "Agent", + task_source_metadata: Optional[Dict[str, Any]] = None, ) -> Task: """Create a new QuickForm task synchronously. @@ -812,6 +818,7 @@ def create_quickform( is_actionable_message_enabled=is_actionable_message_enabled, actionable_message_metadata=actionable_message_metadata, source_name=source_name, + task_source_metadata=task_source_metadata, ) response = self.request( diff --git a/packages/uipath-platform/tests/services/test_actions_service.py b/packages/uipath-platform/tests/services/test_actions_service.py index c70f2557f..6102c6fab 100644 --- a/packages/uipath-platform/tests/services/test_actions_service.py +++ b/packages/uipath-platform/tests/services/test_actions_service.py @@ -1071,6 +1071,28 @@ async def test_create_async_merges_task_source_metadata( assert task_source_metadata["InstanceId"] == "trace-1" +def test_create_quickform_merges_task_source_metadata( + qf_runner: Any, jit_debug_env: None +) -> None: + _, body = qf_runner(task_source_metadata={"ParentSpanId": "a3c27222815e80ca"}) + + task_source_metadata = body["taskSource"]["taskSourceMetadata"] + assert task_source_metadata["ParentSpanId"] == "a3c27222815e80ca" + assert task_source_metadata["InstanceId"] == "trace-1" + + +async def test_create_quickform_async_merges_task_source_metadata( + qf_runner_async: Any, jit_debug_env: None +) -> None: + _, body = await qf_runner_async( + task_source_metadata={"ParentSpanId": "a3c27222815e80ca"} + ) + + task_source_metadata = body["taskSource"]["taskSourceMetadata"] + assert task_source_metadata["ParentSpanId"] == "a3c27222815e80ca" + assert task_source_metadata["InstanceId"] == "trace-1" + + def test_create_omits_extra_task_source_metadata_when_unset( httpx_mock: HTTPXMock, service: TasksService, From f0e386cb15cfa8a69ddd121272d58620daae8fab Mon Sep 17 00:00:00 2001 From: Sahil Wakode Date: Mon, 19 Oct 2026 17:37:33 +0530 Subject: [PATCH 02/11] test(tasks): share the quick form task source assertions --- .../tests/services/test_actions_service.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/uipath-platform/tests/services/test_actions_service.py b/packages/uipath-platform/tests/services/test_actions_service.py index 6102c6fab..58e571c47 100644 --- a/packages/uipath-platform/tests/services/test_actions_service.py +++ b/packages/uipath-platform/tests/services/test_actions_service.py @@ -1071,14 +1071,17 @@ async def test_create_async_merges_task_source_metadata( assert task_source_metadata["InstanceId"] == "trace-1" +def _assert_quickform_task_source(body: dict[str, Any]) -> None: + task_source_metadata = body["taskSource"]["taskSourceMetadata"] + assert task_source_metadata["ParentSpanId"] == "a3c27222815e80ca" + assert task_source_metadata["InstanceId"] == "trace-1" + + def test_create_quickform_merges_task_source_metadata( qf_runner: Any, jit_debug_env: None ) -> None: _, body = qf_runner(task_source_metadata={"ParentSpanId": "a3c27222815e80ca"}) - - task_source_metadata = body["taskSource"]["taskSourceMetadata"] - assert task_source_metadata["ParentSpanId"] == "a3c27222815e80ca" - assert task_source_metadata["InstanceId"] == "trace-1" + _assert_quickform_task_source(body) async def test_create_quickform_async_merges_task_source_metadata( @@ -1087,10 +1090,7 @@ async def test_create_quickform_async_merges_task_source_metadata( _, body = await qf_runner_async( task_source_metadata={"ParentSpanId": "a3c27222815e80ca"} ) - - task_source_metadata = body["taskSource"]["taskSourceMetadata"] - assert task_source_metadata["ParentSpanId"] == "a3c27222815e80ca" - assert task_source_metadata["InstanceId"] == "trace-1" + _assert_quickform_task_source(body) def test_create_omits_extra_task_source_metadata_when_unset( From dcf0fe9435a7144c8c796ff6a975ff0c41c5e5a7 Mon Sep 17 00:00:00 2001 From: Sahil Wakode Date: Thu, 22 Oct 2026 14:52:54 +0530 Subject: [PATCH 03/11] refactor(tasks): accept task_source_metadata only on create_quickform_async --- .../platform/action_center/_tasks_service.py | 2 -- .../tests/services/test_actions_service.py | 18 ++++-------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py index 8a956b4f0..7032c4249 100644 --- a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py +++ b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py @@ -799,7 +799,6 @@ def create_quickform( actionable_message_metadata: Optional[Dict[str, Any]] = None, creator_job_key: Optional[str] = None, source_name: str = "Agent", - task_source_metadata: Optional[Dict[str, Any]] = None, ) -> Task: """Create a new QuickForm task synchronously. @@ -818,7 +817,6 @@ def create_quickform( is_actionable_message_enabled=is_actionable_message_enabled, actionable_message_metadata=actionable_message_metadata, source_name=source_name, - task_source_metadata=task_source_metadata, ) response = self.request( diff --git a/packages/uipath-platform/tests/services/test_actions_service.py b/packages/uipath-platform/tests/services/test_actions_service.py index 58e571c47..63538cf99 100644 --- a/packages/uipath-platform/tests/services/test_actions_service.py +++ b/packages/uipath-platform/tests/services/test_actions_service.py @@ -1071,26 +1071,16 @@ async def test_create_async_merges_task_source_metadata( assert task_source_metadata["InstanceId"] == "trace-1" -def _assert_quickform_task_source(body: dict[str, Any]) -> None: - task_source_metadata = body["taskSource"]["taskSourceMetadata"] - assert task_source_metadata["ParentSpanId"] == "a3c27222815e80ca" - assert task_source_metadata["InstanceId"] == "trace-1" - - -def test_create_quickform_merges_task_source_metadata( - qf_runner: Any, jit_debug_env: None -) -> None: - _, body = qf_runner(task_source_metadata={"ParentSpanId": "a3c27222815e80ca"}) - _assert_quickform_task_source(body) - - async def test_create_quickform_async_merges_task_source_metadata( qf_runner_async: Any, jit_debug_env: None ) -> None: _, body = await qf_runner_async( task_source_metadata={"ParentSpanId": "a3c27222815e80ca"} ) - _assert_quickform_task_source(body) + + task_source_metadata = body["taskSource"]["taskSourceMetadata"] + assert task_source_metadata["ParentSpanId"] == "a3c27222815e80ca" + assert task_source_metadata["InstanceId"] == "trace-1" def test_create_omits_extra_task_source_metadata_when_unset( From be5fdc3e6bd08346486a140cf56e1eedaf473dea Mon Sep 17 00:00:00 2001 From: Sahil Wakode Date: Thu, 22 Oct 2026 15:11:03 +0530 Subject: [PATCH 04/11] chore(tasks): bump uipath-platform to 0.2.44 --- packages/uipath-platform/pyproject.toml | 2 +- packages/uipath-platform/uv.lock | 2 +- packages/uipath/uv.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/uipath-platform/pyproject.toml b/packages/uipath-platform/pyproject.toml index 3f197bee9..00f93d26a 100644 --- a/packages/uipath-platform/pyproject.toml +++ b/packages/uipath-platform/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-platform" -version = "0.2.43" +version = "0.2.44" description = "HTTP client library for programmatic access to UiPath Platform" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath-platform/uv.lock b/packages/uipath-platform/uv.lock index e59fdb62d..fcbcc4b98 100644 --- a/packages/uipath-platform/uv.lock +++ b/packages/uipath-platform/uv.lock @@ -1095,7 +1095,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.2.43" +version = "0.2.44" source = { editable = "." } dependencies = [ { name = "anyio" }, diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index 6ef2b0af7..e2e199148 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2762,7 +2762,7 @@ wheels = [ [[package]] name = "uipath-platform" -version = "0.2.43" +version = "0.2.44" source = { editable = "../uipath-platform" } dependencies = [ { name = "anyio" }, From 51178b51308dec02e27e0dee4274160626896469 Mon Sep 17 00:00:00 2001 From: Sahil Wakode Date: Mon, 26 Oct 2026 20:49:52 +0530 Subject: [PATCH 05/11] feat(tasks): send the current span as ParentSpanId in taskSourceMetadata [ACTN-11909] _apply_task_source adds ParentSpanId from the current span, so every task the SDK creates carries it. The task_source_metadata parameter on create_quickform_async is no longer needed and is removed. --- .../platform/action_center/_tasks_service.py | 13 +++++++------ .../tests/services/test_actions_service.py | 15 ++++++++++----- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py index 7032c4249..103e3422a 100644 --- a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py +++ b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py @@ -3,11 +3,14 @@ import uuid from typing import Any, Dict, List, Optional +from opentelemetry import trace +from opentelemetry.trace import format_span_id from uipath.core.feature_flags import ( JIT_ESCALATION_APPS_FEATURE_FLAG, FeatureFlags, ) from uipath.core.tracing import traced +from uipath.core.tracing.span_utils import UiPathSpanUtils from uipath.platform.constants import ( ENV_TENANT_ID, @@ -210,6 +213,8 @@ def _apply_task_source( trace_id = UiPathConfig.trace_id if not (project_id and trace_id): return + span = UiPathSpanUtils.get_external_current_span() or trace.get_current_span() + span_id = span.get_span_context().span_id payload["taskSource"] = { "sourceName": source_name, "sourceId": project_id, @@ -218,6 +223,7 @@ def _apply_task_source( "FolderKey": UiPathConfig.folder_key, "JobKey": UiPathConfig.job_key, "ProcessKey": UiPathConfig.process_uuid, + **({"ParentSpanId": format_span_id(span_id)} if span_id else {}), **(custom_metadata or {}), }, "jobId": UiPathConfig.job_key, @@ -274,7 +280,6 @@ def _create_quickform_spec( is_actionable_message_enabled: Optional[bool] = None, actionable_message_metadata: Optional[Dict[str, Any]] = None, source_name: str = "Agent", - task_source_metadata: Optional[Dict[str, Any]] = None, ) -> RequestSpec: """Build the RequestSpec for Orchestrator's GenericTasks/CreateTask endpoint. @@ -305,7 +310,7 @@ def _create_quickform_spec( ) if actionable_message_metadata is not None: json_payload["actionableMessageMetaData"] = actionable_message_metadata - _apply_task_source(json_payload, source_name, custom_metadata=task_source_metadata) + _apply_task_source(json_payload, source_name) return RequestSpec( method="POST", @@ -705,7 +710,6 @@ async def create_quickform_async( actionable_message_metadata: Optional[Dict[str, Any]] = None, creator_job_key: Optional[str] = None, source_name: str = "Agent", - task_source_metadata: Optional[Dict[str, Any]] = None, ) -> Task: """Creates a new QuickForm task asynchronously. @@ -739,8 +743,6 @@ async def create_quickform_async( creator_job_key: Optional. Identifies the job that triggered the inline schema creation/upsert. source_name: Source name on TaskSource. Defaults to 'Agent'. - task_source_metadata: Optional extra keys merged into - taskSource.taskSourceMetadata, same as in `create_async()`. Returns: Task: The created task object. @@ -758,7 +760,6 @@ async def create_quickform_async( is_actionable_message_enabled=is_actionable_message_enabled, actionable_message_metadata=actionable_message_metadata, source_name=source_name, - task_source_metadata=task_source_metadata, ) response = await self.request_async( diff --git a/packages/uipath-platform/tests/services/test_actions_service.py b/packages/uipath-platform/tests/services/test_actions_service.py index 63538cf99..18b2b6a92 100644 --- a/packages/uipath-platform/tests/services/test_actions_service.py +++ b/packages/uipath-platform/tests/services/test_actions_service.py @@ -2,7 +2,9 @@ from typing import Any import pytest +from opentelemetry.trace import NonRecordingSpan, SpanContext from pytest_httpx import HTTPXMock +from uipath.core.tracing.span_utils import UiPathSpanUtils from uipath.platform import UiPathApiConfig, UiPathExecutionContext from uipath.platform.action_center import Task @@ -1071,16 +1073,19 @@ async def test_create_async_merges_task_source_metadata( assert task_source_metadata["InstanceId"] == "trace-1" -async def test_create_quickform_async_merges_task_source_metadata( - qf_runner_async: Any, jit_debug_env: None +async def test_create_quickform_async_sends_the_current_span_as_parent_span_id( + qf_runner_async: Any, jit_debug_env: None, monkeypatch: pytest.MonkeyPatch ) -> None: - _, body = await qf_runner_async( - task_source_metadata={"ParentSpanId": "a3c27222815e80ca"} + span = NonRecordingSpan( + SpanContext(trace_id=1, span_id=0xA3C27222815E80CA, is_remote=False) ) + monkeypatch.setattr(UiPathSpanUtils, "get_external_current_span", lambda: span) + monkeypatch.setenv("UIPATH_TRACE_ID", "4bf92f3577b34da6a3ce929d0e0e4736") + + _, body = await qf_runner_async() task_source_metadata = body["taskSource"]["taskSourceMetadata"] assert task_source_metadata["ParentSpanId"] == "a3c27222815e80ca" - assert task_source_metadata["InstanceId"] == "trace-1" def test_create_omits_extra_task_source_metadata_when_unset( From 61f602a3a7ee8690fd72fcf6d3137daa866b0316 Mon Sep 17 00:00:00 2001 From: Sahil Wakode Date: Tue, 27 Oct 2026 20:41:05 +0530 Subject: [PATCH 06/11] fix(tasks): never let the span lookup fail task creation [ACTN-11909] ParentSpanId is added only for a valid, non-zero span id. Any error while reading the span is logged at debug level and the task is created without ParentSpanId. --- .../platform/action_center/_tasks_service.py | 19 ++++++-- .../tests/services/test_actions_service.py | 47 ++++++++++++++++++- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py index 103e3422a..b0dd9c8da 100644 --- a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py +++ b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py @@ -1,4 +1,5 @@ import asyncio +import logging import os import uuid from typing import Any, Dict, List, Optional @@ -26,6 +27,8 @@ from .task_schema import TaskSchema from .tasks import Task, TaskRecipient, TaskRecipientType +logger = logging.getLogger(__name__) + def _ensure_string_value(value: Any) -> str: """Convert any value to a string for use in field Value.""" @@ -193,6 +196,17 @@ def _apply_priority_labels_and_actionable_toggle( payload["isActionableMessageEnabled"] = is_actionable_message_enabled +def _current_span_id() -> Optional[str]: + try: + span = UiPathSpanUtils.get_external_current_span() or trace.get_current_span() + span_context = span.get_span_context() + if span_context.is_valid and span_context.span_id: + return format_span_id(span_context.span_id) + except Exception: + logger.debug("Could not read the current span id", exc_info=True) + return None + + def _apply_task_source( payload: Dict[str, Any], source_name: str, @@ -213,8 +227,7 @@ def _apply_task_source( trace_id = UiPathConfig.trace_id if not (project_id and trace_id): return - span = UiPathSpanUtils.get_external_current_span() or trace.get_current_span() - span_id = span.get_span_context().span_id + parent_span_id = _current_span_id() payload["taskSource"] = { "sourceName": source_name, "sourceId": project_id, @@ -223,7 +236,7 @@ def _apply_task_source( "FolderKey": UiPathConfig.folder_key, "JobKey": UiPathConfig.job_key, "ProcessKey": UiPathConfig.process_uuid, - **({"ParentSpanId": format_span_id(span_id)} if span_id else {}), + **({"ParentSpanId": parent_span_id} if parent_span_id else {}), **(custom_metadata or {}), }, "jobId": UiPathConfig.job_key, diff --git a/packages/uipath-platform/tests/services/test_actions_service.py b/packages/uipath-platform/tests/services/test_actions_service.py index 18b2b6a92..f4505d1bd 100644 --- a/packages/uipath-platform/tests/services/test_actions_service.py +++ b/packages/uipath-platform/tests/services/test_actions_service.py @@ -1,14 +1,18 @@ import json from typing import Any +from unittest.mock import MagicMock import pytest -from opentelemetry.trace import NonRecordingSpan, SpanContext +from opentelemetry.trace import INVALID_SPAN, NonRecordingSpan, SpanContext from pytest_httpx import HTTPXMock from uipath.core.tracing.span_utils import UiPathSpanUtils from uipath.platform import UiPathApiConfig, UiPathExecutionContext from uipath.platform.action_center import Task -from uipath.platform.action_center._tasks_service import TasksService +from uipath.platform.action_center._tasks_service import ( + TasksService, + _apply_task_source, +) from uipath.platform.action_center.tasks import TaskRecipient, TaskRecipientType from uipath.platform.constants import HEADER_USER_AGENT @@ -1088,6 +1092,45 @@ async def test_create_quickform_async_sends_the_current_span_as_parent_span_id( assert task_source_metadata["ParentSpanId"] == "a3c27222815e80ca" +class _BrokenSpan: + def get_span_context(self) -> Any: + raise RuntimeError("span broke") + + +def _span_with_id(span_id: Any) -> Any: + span = MagicMock() + span.get_span_context.return_value.span_id = span_id + span.get_span_context.return_value.is_valid = True + return span + + +@pytest.mark.parametrize( + "span", + [ + INVALID_SPAN, + _BrokenSpan(), + _span_with_id(0), + _span_with_id(None), + _span_with_id(""), + _span_with_id("abc"), + _span_with_id(1.5), + NonRecordingSpan(SpanContext(trace_id=1, span_id=-1, is_remote=False)), + NonRecordingSpan(SpanContext(trace_id=1, span_id=2**64, is_remote=False)), + ], +) +def test_apply_task_source_without_a_valid_span_omits_parent_span_id( + jit_debug_env: None, monkeypatch: pytest.MonkeyPatch, span: Any +) -> None: + monkeypatch.setattr(UiPathSpanUtils, "get_external_current_span", lambda: span) + payload: dict[str, Any] = {} + + _apply_task_source(payload, "Agent") + + task_source_metadata = payload["taskSource"]["taskSourceMetadata"] + assert task_source_metadata["InstanceId"] == "trace-1" + assert "ParentSpanId" not in task_source_metadata + + def test_create_omits_extra_task_source_metadata_when_unset( httpx_mock: HTTPXMock, service: TasksService, From 678f0d25b47dd12318db5b0b6dfcfe494b6cc2fe Mon Sep 17 00:00:00 2001 From: Sahil Wakode Date: Mon, 2 Nov 2026 11:37:26 +0530 Subject: [PATCH 07/11] refactor(tasks): move the current span lookup into _SpanUtils [ACTN-11909] The tasks service now calls _SpanUtils.current_span_id() instead of its own copy of the lookup. --- .../platform/action_center/_tasks_service.py | 20 ++----------------- .../src/uipath/platform/common/_span_utils.py | 19 +++++++++++++++++- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py index b0dd9c8da..627280deb 100644 --- a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py +++ b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py @@ -1,17 +1,13 @@ import asyncio -import logging import os import uuid from typing import Any, Dict, List, Optional -from opentelemetry import trace -from opentelemetry.trace import format_span_id from uipath.core.feature_flags import ( JIT_ESCALATION_APPS_FEATURE_FLAG, FeatureFlags, ) from uipath.core.tracing import traced -from uipath.core.tracing.span_utils import UiPathSpanUtils from uipath.platform.constants import ( ENV_TENANT_ID, @@ -24,11 +20,10 @@ from ..common._execution_context import UiPathExecutionContext from ..common._folder_context import FolderContext, header_folder from ..common._models import Endpoint, RequestSpec +from ..common._span_utils import _SpanUtils from .task_schema import TaskSchema from .tasks import Task, TaskRecipient, TaskRecipientType -logger = logging.getLogger(__name__) - def _ensure_string_value(value: Any) -> str: """Convert any value to a string for use in field Value.""" @@ -196,17 +191,6 @@ def _apply_priority_labels_and_actionable_toggle( payload["isActionableMessageEnabled"] = is_actionable_message_enabled -def _current_span_id() -> Optional[str]: - try: - span = UiPathSpanUtils.get_external_current_span() or trace.get_current_span() - span_context = span.get_span_context() - if span_context.is_valid and span_context.span_id: - return format_span_id(span_context.span_id) - except Exception: - logger.debug("Could not read the current span id", exc_info=True) - return None - - def _apply_task_source( payload: Dict[str, Any], source_name: str, @@ -227,7 +211,7 @@ def _apply_task_source( trace_id = UiPathConfig.trace_id if not (project_id and trace_id): return - parent_span_id = _current_span_id() + parent_span_id = _SpanUtils.current_span_id() payload["taskSource"] = { "sourceName": source_name, "sourceId": project_id, diff --git a/packages/uipath-platform/src/uipath/platform/common/_span_utils.py b/packages/uipath-platform/src/uipath/platform/common/_span_utils.py index 111dd59ec..897d6ae56 100644 --- a/packages/uipath-platform/src/uipath/platform/common/_span_utils.py +++ b/packages/uipath-platform/src/uipath/platform/common/_span_utils.py @@ -11,8 +11,9 @@ from typing import Any, Dict, List, Optional, TypeVar from opentelemetry import context as context_api +from opentelemetry import trace from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor -from opentelemetry.trace import StatusCode +from opentelemetry.trace import StatusCode, format_span_id from pydantic import BaseModel, ConfigDict, Field from uipath.core.serialization import serialize_json @@ -371,6 +372,22 @@ def normalize_span_id(value: str) -> str: else: raise ValueError(f"Invalid span ID format: {value}") + @staticmethod + def current_span_id() -> Optional[str]: + from uipath.core.tracing.span_utils import UiPathSpanUtils + + try: + span = ( + UiPathSpanUtils.get_external_current_span() or trace.get_current_span() + ) + span_context = span.get_span_context() + span_id = span_context.span_id + if span_context.is_valid and isinstance(span_id, int) and span_id: + return format_span_id(span_id) + except Exception: + logger.debug("Could not read the current span id", exc_info=True) + return None + @staticmethod def otel_span_to_uipath_span( otel_span: ReadableSpan, From b16f42a7f7ef42a259c942ea95f5dafba5d122cd Mon Sep 17 00:00:00 2001 From: Sahil Wakode Date: Tue, 3 Nov 2026 15:17:02 +0530 Subject: [PATCH 08/11] refactor(tracing): read the current span through _SpanUtils in _inject_trace_context [ACTN-11909] _inject_trace_context now gets the span from _SpanUtils.current_span(), the same lookup the tasks service uses. The header logic is unchanged. --- .../src/uipath/platform/common/_base_service.py | 6 +----- .../src/uipath/platform/common/_span_utils.py | 11 ++++++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/uipath-platform/src/uipath/platform/common/_base_service.py b/packages/uipath-platform/src/uipath/platform/common/_base_service.py index ee1b17e9a..959d135f7 100644 --- a/packages/uipath-platform/src/uipath/platform/common/_base_service.py +++ b/packages/uipath-platform/src/uipath/platform/common/_base_service.py @@ -132,14 +132,10 @@ def _inject_trace_context(headers: dict[str, str]) -> None: if HEADER_TRACEPARENT_ID in headers: return - from uipath.core.tracing.span_utils import UiPathSpanUtils - from ._config import UiPathConfig from ._span_utils import _SpanUtils - llmops_span = UiPathSpanUtils.get_external_current_span() - span = llmops_span or trace.get_current_span() - ctx = span.get_span_context() + ctx = _SpanUtils.current_span().get_span_context() if not (ctx.trace_id and ctx.span_id): return diff --git a/packages/uipath-platform/src/uipath/platform/common/_span_utils.py b/packages/uipath-platform/src/uipath/platform/common/_span_utils.py index 897d6ae56..ee205e0c3 100644 --- a/packages/uipath-platform/src/uipath/platform/common/_span_utils.py +++ b/packages/uipath-platform/src/uipath/platform/common/_span_utils.py @@ -373,14 +373,15 @@ def normalize_span_id(value: str) -> str: raise ValueError(f"Invalid span ID format: {value}") @staticmethod - def current_span_id() -> Optional[str]: + def current_span() -> trace.Span: from uipath.core.tracing.span_utils import UiPathSpanUtils + return UiPathSpanUtils.get_external_current_span() or trace.get_current_span() + + @staticmethod + def current_span_id() -> Optional[str]: try: - span = ( - UiPathSpanUtils.get_external_current_span() or trace.get_current_span() - ) - span_context = span.get_span_context() + span_context = _SpanUtils.current_span().get_span_context() span_id = span_context.span_id if span_context.is_valid and isinstance(span_id, int) and span_id: return format_span_id(span_id) From ab1f4391592f3f0fe192c62c3309266cc3452b4d Mon Sep 17 00:00:00 2001 From: Sahil Wakode Date: Tue, 3 Nov 2026 17:48:22 +0530 Subject: [PATCH 09/11] chore(tasks): bump uipath-platform to 0.3.0 --- packages/uipath-platform/pyproject.toml | 2 +- packages/uipath-platform/uv.lock | 2 +- packages/uipath/uv.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/uipath-platform/pyproject.toml b/packages/uipath-platform/pyproject.toml index 00f93d26a..56920f06c 100644 --- a/packages/uipath-platform/pyproject.toml +++ b/packages/uipath-platform/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-platform" -version = "0.2.44" +version = "0.3.0" description = "HTTP client library for programmatic access to UiPath Platform" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath-platform/uv.lock b/packages/uipath-platform/uv.lock index fcbcc4b98..fc04aa5b1 100644 --- a/packages/uipath-platform/uv.lock +++ b/packages/uipath-platform/uv.lock @@ -1095,7 +1095,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.2.44" +version = "0.3.0" source = { editable = "." } dependencies = [ { name = "anyio" }, diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index e2e199148..b4b6334e7 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2762,7 +2762,7 @@ wheels = [ [[package]] name = "uipath-platform" -version = "0.2.44" +version = "0.3.0" source = { editable = "../uipath-platform" } dependencies = [ { name = "anyio" }, From 6e75b72a7579bdaaf4891fd7caa42e937d28ebbb Mon Sep 17 00:00:00 2001 From: Sahil Wakode Date: Wed, 4 Nov 2026 17:07:25 +0530 Subject: [PATCH 10/11] chore(uipath): allow uipath-platform 0.3.x and bump to 2.15.0 [ACTN-11909] --- packages/uipath/pyproject.toml | 4 ++-- packages/uipath/uv.lock | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index 094ddcd78..03171b551 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -1,13 +1,13 @@ [project] name = "uipath" -version = "2.14.49" +version = "2.15.0" 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" dependencies = [ "uipath-core>=0.5.33, <0.6.0", "uipath-runtime>=0.13.5, <0.14.0", - "uipath-platform>=0.2.37, <0.3.0", + "uipath-platform>=0.3.0, <0.4.0", "uipath-ipc>=2.5.1, <2.6.0", "click>=8.3.3, <9.0.0", "httpx>=0.28.1", diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index b4b6334e7..35b84ec80 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2599,7 +2599,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.14.49" +version = "2.15.0" source = { editable = "." } dependencies = [ { name = "applicationinsights" }, From 303010dfb4d783eca2b15d0b3744dfaade0d638f Mon Sep 17 00:00:00 2001 From: Sahil Wakode Date: Wed, 4 Nov 2026 17:56:01 +0530 Subject: [PATCH 11/11] chore(uipath): bump scaffold pin minor to 2.15 [ACTN-11909] --- packages/uipath/src/uipath/_cli/cli_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/uipath/src/uipath/_cli/cli_new.py b/packages/uipath/src/uipath/_cli/cli_new.py index 59375494d..7035bff03 100644 --- a/packages/uipath/src/uipath/_cli/cli_new.py +++ b/packages/uipath/src/uipath/_cli/cli_new.py @@ -27,7 +27,7 @@ # installed release itself: a Robot refuses a project on an older `uipath` # than it supports, so a scaffold must never ask for less than the CLI that # wrote it. -UIPATH_SCAFFOLD_MINOR = "2.14" +UIPATH_SCAFFOLD_MINOR = "2.15" def generate_script(target_directory):