From 134ea614a8d3936aae7356e79f4210cfc274bad2 Mon Sep 17 00:00:00 2001 From: Alexandru Nica Date: Thu, 30 Jul 2026 17:54:52 +0300 Subject: [PATCH 1/6] docs(solution): document the connection-repoint prune and role member lookup uipath-solution: `resources refresh` now reports `Pruned` and removes the connection resource orphaned when a binding is repointed to a different connection GUID. Documents what the prune does and does not touch, and corrects the two places that stated refresh never removes anything. Also notes that an Enabled, pingable connection can still be missing from the Resource Catalog, since RCS indexing is per folder. uipath-platform: documents `roles users list` as the forward "who holds this role" lookup now that it scopes to actual members, so auditing is one call per role instead of a directory sweep per principal. Co-Authored-By: Claude Opus 5 --- .../orchestrator/setup-environment.md | 9 +++++++++ .../references/develop-solution.md | 18 ++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/skills/uipath-platform/references/orchestrator/setup-environment.md b/skills/uipath-platform/references/orchestrator/setup-environment.md index f7a6554386..39f0a955f0 100644 --- a/skills/uipath-platform/references/orchestrator/setup-environment.md +++ b/skills/uipath-platform/references/orchestrator/setup-environment.md @@ -146,6 +146,15 @@ uip or roles delete --output json `roles user-permissions` is the right command for "what can this user actually do here" debugging; it accounts for inherited folder roles and tenant overrides. `roles user-roles` is the inverse — given a user, which role assignments exist. +For the forward lookup — "who holds this role" — use `roles users list`: + +```bash +# Principals assigned to a role (users, groups, robots, external apps) +uip or roles users list --output json +``` + +`Pagination.Total` is the role's full membership count, so a role with no assignees returns `Data: []` and `Total: 0`. Auditing a whole tenant is therefore one call per role, not a directory sweep per principal. + ### Step 4: Import Users from Identity Service Principals are managed in Identity Service (IS), not in Orchestrator. `users import` is the **single integration point** between IS and the tenant: it references an existing IS principal and provisions the matching tenant user record. Everything downstream (`users assign`, `users assign-roles`, `roles assign`, etc.) takes the resolved Orchestrator user-key — no further IS round-trips. (The legacy `users create` / `users delete` commands are gone — they called endpoints reserved for `ProvisionType=Manual`, which is not how cloud or IS-backed users are managed.) diff --git a/skills/uipath-solution/references/develop-solution.md b/skills/uipath-solution/references/develop-solution.md index fe2fcd91fb..a0777827c3 100644 --- a/skills/uipath-solution/references/develop-solution.md +++ b/skills/uipath-solution/references/develop-solution.md @@ -134,6 +134,7 @@ uip solution resources refresh --solution-folder ./InvoiceAutomation --output js | `Created` | New local skeletons created (resource didn't exist in cloud) | | `Imported` | Cloud resources imported into the solution (artefact files written + linked) | | `Skipped` | Resources already tracked in the solution | +| `Pruned` | Connection resources dropped because a binding was repointed away from them (see [Repointing a connection](#repointing-a-connection)) | | `Warnings` | Bindings that couldn't be resolved (logged for follow-up) | ### What `refresh` actually does @@ -168,7 +169,20 @@ When a name (e.g. `orders` queue) exists in multiple cloud folders, refresh pref The placeholder `solution_folder` (and `.`) in a binding's folder field means "no folder" / tenant scope — they're not real cloud folders. -> For single-resource mutations that don't need a full project scan, see [Step 9: Add a Resource Atomically](#step-9-add-a-resource-atomically), [Step 10: Remove a Resource](#step-10-remove-a-resource), and [Step 11: Edit a Resource](#step-11-edit-a-resource). `refresh` and these solve different problems — `refresh` reconciles every binding in every project (and **never overwrites** a resource already in the solution); `add`/`remove`/`edit` operate on one resource at a time. To change an existing resource's spec, `edit` is the only path — `refresh` won't. +### Repointing a connection + +Changing a connector activity to a *different* connection (a different connection GUID) is the one case where `refresh` removes something. Repoint the binding, run `bindings sync` on the project, then `refresh`: + +- the new connection is imported under the connection's own name, and +- the resource for the old GUID is dropped, reported as `Pruned: 1`. + +You end up with one connection resource keyed to the new GUID — no `_1.json` duplicate, and no stale entry lingering in `resources list` or `debug_overwrites.json`. + +The prune is deliberately narrow. It only fires for a **connection** resource that shares the incoming connection's name (or a `_N` variant of it) and that **no binding references any more**. A connection another binding still points at is kept, and so is one whose name is unrelated — even if nothing references it. Every other kind stays import-only. + +> A connection can be `Enabled` and pingable and still not be importable: the Resource Catalog is indexed per folder, so `refresh` may warn `Connection not found in Resource Catalog in the required folder ""` and import nothing. Membership in `uip is connections list` does not guarantee a connection is refresh-resolvable from a given `folderPath`. + +> For single-resource mutations that don't need a full project scan, see [Step 9: Add a Resource Atomically](#step-9-add-a-resource-atomically), [Step 10: Remove a Resource](#step-10-remove-a-resource), and [Step 11: Edit a Resource](#step-11-edit-a-resource). `refresh` and these solve different problems — `refresh` reconciles every binding in every project (and **never overwrites** the spec of a resource already in the solution — its one removal is the orphan of a repointed connection); `add`/`remove`/`edit` operate on one resource at a time. To change an existing resource's spec, `edit` is the only path — `refresh` won't. ## Step 8: Get a Single Resource Configuration @@ -350,7 +364,7 @@ If the key isn't in the local solution, the command exits with `Failure` and `Re ## Step 11: Edit a Resource -Change a resource's `spec` properties by key. This is the only command that mutates an existing resource — `refresh` is import-only (it skips resources already in the solution, never overwrites them). +Change a resource's `spec` properties by key. This is the only command that mutates an existing resource's spec — `refresh` is import-only (it skips resources already in the solution, never overwrites their spec). The one thing `refresh` does remove is a connection resource left orphaned by a repoint; see [Repointing a connection](#repointing-a-connection). ```bash # Patch a single spec property From ba12886017bd49adc26146cf1eaf4a9951a5da91 Mon Sep 17 00:00:00 2001 From: Alexandru Nica Date: Fri, 31 Jul 2026 16:07:22 +0300 Subject: [PATCH 2/6] docs(platform): correct the environment-variables format on processes Documented as a JSON object; Orchestrator actually stores newline-separated KEY=VALUE pairs. Verified against a live tenant: create and update both round-trip 'FOO=bar\nBAZ=qux'. Adds the clear-them example and flags that jobs start still validates JSON while its runtime contract is unconfirmed. Co-Authored-By: Claude Opus 5 --- .../references/orchestrator/run-jobs.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/skills/uipath-platform/references/orchestrator/run-jobs.md b/skills/uipath-platform/references/orchestrator/run-jobs.md index de2a41fe91..d5f4739cf9 100644 --- a/skills/uipath-platform/references/orchestrator/run-jobs.md +++ b/skills/uipath-platform/references/orchestrator/run-jobs.md @@ -92,7 +92,7 @@ Key options: | `--specific-priority <1-100>` | Numeric priority override (mutually exclusive with `--job-priority`). Use when you need fine-grained ordering inside the same priority bucket. | | `--robot-size ` | Cloud robot sizing for serverless runtimes | | `--input-arguments ` | Default input arguments (merged with per-job inputs) | -| `--environment-variables ` | Default environment variables (merged with per-job env) | +| `--environment-variables ` | Default environment variables — newline-separated `KEY=VALUE` pairs, **not** JSON (merged with per-job env) | | `--tags ` | Comma-separated tags for filtering | | `--hidden-for-attended` / `--visible-for-attended` | Toggle visibility to attended robot users | | `--auto-create-triggers` / `--no-auto-create-triggers` | Auto-create connected triggers on deploy | @@ -115,6 +115,13 @@ uip or processes resources --output json # plus --healing-agent / --no-healing-agent (Autopilot for Robots toggle). uip or processes update --description "Updated description" --output json +# Environment variables are newline-separated KEY=VALUE pairs, not JSON. +uip or processes update --environment-variables $'API_HOST=api.example.com\nRETRIES=3' --output json + +# Pass '' to clear them (Orchestrator reads an empty body as "leave them alone", +# so the CLI sends a bare newline to make the clear actually happen). +uip or processes update --environment-variables '' --output json + # Walk the package version history (every package version this release ever pointed at) uip or processes version-history --output json @@ -154,7 +161,7 @@ Key options: - `--user-keys ` / `--machine-keys ` — comma-separated GUIDs to pin the job to specific identities. With `--strategy ModernJobsCount` they restrict the candidate pool; with `Specific` they're required. - `--healing-agent` — enable Autopilot for Robots (Healing Agent) just for this job, regardless of the process-level `--healing-agent` setting on `processes update`. Useful for one-off self-healing without flipping the process default. - `--reference ` — user-set reference (free-form string) attached to the job. Useful for correlation with external systems. -- `--environment-variables ` — JSON object of per-job environment variables. Merged on top of folder-/process-level env. +- `--environment-variables ` — per-job environment variables, merged on top of folder-/process-level env. Note: `jobs start` still validates this as JSON, while the process-level flag takes newline-separated `KEY=VALUE` pairs. The job-level field reads back empty for both shapes on a tenant with no machine to execute the job, so which one the runtime honours is unconfirmed — prefer setting them on the process with `processes update` until that is settled. - `--run-as-me` — run under the caller's identity instead of resolving an unattended robot account in the folder. - `--wait-for-completion` + `--timeout ` (default 300) + `--poll-interval ` (default 5) — poll until the job reaches a terminal state. - `--output-dir ` + `--no-download` — when `--wait-for-completion` is set, the CLI downloads the job's `OutputFile` to this directory automatically. Pass `--no-download` to opt out. From 011448e88b6804d0f6ca8239426ded697dc851df Mon Sep 17 00:00:00 2001 From: Alexandru Nica Date: Fri, 31 Jul 2026 17:11:41 +0300 Subject: [PATCH 3/6] docs(solution): document the connection-repoint duplicate as expected Replaces the earlier description of a prune that is not being implemented. Refresh stays import-only: repointing a binding to a different connection GUID imports the new connection as _1 and leaves the resource for the old GUID in place. Documents that shape and points at `resources remove` for cleanup, which is what UV-15516 asked for as the alternative to changing refresh. Co-Authored-By: Claude Opus 5 --- .../references/develop-solution.md | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/skills/uipath-solution/references/develop-solution.md b/skills/uipath-solution/references/develop-solution.md index a0777827c3..ec417d0d12 100644 --- a/skills/uipath-solution/references/develop-solution.md +++ b/skills/uipath-solution/references/develop-solution.md @@ -134,7 +134,6 @@ uip solution resources refresh --solution-folder ./InvoiceAutomation --output js | `Created` | New local skeletons created (resource didn't exist in cloud) | | `Imported` | Cloud resources imported into the solution (artefact files written + linked) | | `Skipped` | Resources already tracked in the solution | -| `Pruned` | Connection resources dropped because a binding was repointed away from them (see [Repointing a connection](#repointing-a-connection)) | | `Warnings` | Bindings that couldn't be resolved (logged for follow-up) | ### What `refresh` actually does @@ -169,20 +168,31 @@ When a name (e.g. `orders` queue) exists in multiple cloud folders, refresh pref The placeholder `solution_folder` (and `.`) in a binding's folder field means "no folder" / tenant scope — they're not real cloud folders. -### Repointing a connection +### Repointing a connection leaves the old resource behind -Changing a connector activity to a *different* connection (a different connection GUID) is the one case where `refresh` removes something. Repoint the binding, run `bindings sync` on the project, then `refresh`: +Repointing a connector activity to a *different* connection (a different connection GUID) does **not** update the existing connection resource in place. Refresh is import-only, so it imports the new connection as a new resource and leaves the old one alone. Because both carry the same connection display name, the newcomer gets suffixed: -- the new connection is imported under the connection's own name, and -- the resource for the old GUID is dropped, reported as `Pruned: 1`. +``` +resources/solution_folder/connection//.json # old GUID — now unreferenced +resources/solution_folder/connection//_1.json # new GUID +``` + +`bindings_v2.json` cleanly references only the new GUID, but both resources stay in `resources list` and in `userProfile//debug_overwrites.json`, and every subsequent refresh reports `Imported: 1` for the new one. + +**Clean it up yourself** — refresh will not, by design: -You end up with one connection resource keyed to the new GUID — no `_1.json` duplicate, and no stale entry lingering in `resources list` or `debug_overwrites.json`. +```bash +# find the key of the resource for the old connection +uip solution resources list --output json + +uip solution resources remove --solution-folder --output json +``` -The prune is deliberately narrow. It only fires for a **connection** resource that shares the incoming connection's name (or a `_N` variant of it) and that **no binding references any more**. A connection another binding still points at is kept, and so is one whose name is unrelated — even if nothing references it. Every other kind stays import-only. +This is intentional. Refresh never removes resources, so it cannot silently drop something you added on purpose. The trade-off is that a repoint needs one manual `remove`. > A connection can be `Enabled` and pingable and still not be importable: the Resource Catalog is indexed per folder, so `refresh` may warn `Connection not found in Resource Catalog in the required folder ""` and import nothing. Membership in `uip is connections list` does not guarantee a connection is refresh-resolvable from a given `folderPath`. -> For single-resource mutations that don't need a full project scan, see [Step 9: Add a Resource Atomically](#step-9-add-a-resource-atomically), [Step 10: Remove a Resource](#step-10-remove-a-resource), and [Step 11: Edit a Resource](#step-11-edit-a-resource). `refresh` and these solve different problems — `refresh` reconciles every binding in every project (and **never overwrites** the spec of a resource already in the solution — its one removal is the orphan of a repointed connection); `add`/`remove`/`edit` operate on one resource at a time. To change an existing resource's spec, `edit` is the only path — `refresh` won't. +> For single-resource mutations that don't need a full project scan, see [Step 9: Add a Resource Atomically](#step-9-add-a-resource-atomically), [Step 10: Remove a Resource](#step-10-remove-a-resource), and [Step 11: Edit a Resource](#step-11-edit-a-resource). `refresh` and these solve different problems — `refresh` reconciles every binding in every project (and **never overwrites** a resource already in the solution); `add`/`remove`/`edit` operate on one resource at a time. To change an existing resource's spec, `edit` is the only path — `refresh` won't. ## Step 8: Get a Single Resource Configuration @@ -364,7 +374,7 @@ If the key isn't in the local solution, the command exits with `Failure` and `Re ## Step 11: Edit a Resource -Change a resource's `spec` properties by key. This is the only command that mutates an existing resource's spec — `refresh` is import-only (it skips resources already in the solution, never overwrites their spec). The one thing `refresh` does remove is a connection resource left orphaned by a repoint; see [Repointing a connection](#repointing-a-connection). +Change a resource's `spec` properties by key. This is the only command that mutates an existing resource — `refresh` is import-only (it skips resources already in the solution, never overwrites them). Repointing a binding to a different connection is not an edit either: refresh imports the new one and leaves the old behind for you to `remove` — see [Repointing a connection leaves the old resource behind](#repointing-a-connection-leaves-the-old-resource-behind). ```bash # Patch a single spec property From dbf51a16193910286fd3b2b24b448c8d4e4a749e Mon Sep 17 00:00:00 2001 From: Alexandru Nica Date: Fri, 31 Jul 2026 17:20:23 +0300 Subject: [PATCH 4/6] docs(platform): jobs start environment variables use the same KEY=VALUE format Was flagged as unconfirmed while the CLI still validated JSON there. The format matches the process-level flag, and jobs start now validates it that way, so the caveat is replaced with the plain contract. Co-Authored-By: Claude Opus 5 --- skills/uipath-platform/references/orchestrator/run-jobs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/uipath-platform/references/orchestrator/run-jobs.md b/skills/uipath-platform/references/orchestrator/run-jobs.md index d5f4739cf9..b9ae50d3b7 100644 --- a/skills/uipath-platform/references/orchestrator/run-jobs.md +++ b/skills/uipath-platform/references/orchestrator/run-jobs.md @@ -161,7 +161,7 @@ Key options: - `--user-keys ` / `--machine-keys ` — comma-separated GUIDs to pin the job to specific identities. With `--strategy ModernJobsCount` they restrict the candidate pool; with `Specific` they're required. - `--healing-agent` — enable Autopilot for Robots (Healing Agent) just for this job, regardless of the process-level `--healing-agent` setting on `processes update`. Useful for one-off self-healing without flipping the process default. - `--reference ` — user-set reference (free-form string) attached to the job. Useful for correlation with external systems. -- `--environment-variables ` — per-job environment variables, merged on top of folder-/process-level env. Note: `jobs start` still validates this as JSON, while the process-level flag takes newline-separated `KEY=VALUE` pairs. The job-level field reads back empty for both shapes on a tenant with no machine to execute the job, so which one the runtime honours is unconfirmed — prefer setting them on the process with `processes update` until that is settled. +- `--environment-variables ` — per-job environment variables as newline-separated `KEY=VALUE` pairs (same shape as the process-level flag, **not** JSON). Merged on top of folder-/process-level env. Malformed lines are rejected before the job is submitted. - `--run-as-me` — run under the caller's identity instead of resolving an unattended robot account in the folder. - `--wait-for-completion` + `--timeout ` (default 300) + `--poll-interval ` (default 5) — poll until the job reaches a terminal state. - `--output-dir ` + `--no-download` — when `--wait-for-completion` is set, the CLI downloads the job's `OutputFile` to this directory automatically. Pass `--no-download` to opt out. From cc09a1d822fe6888190b9e2554982fd681ccfe98 Mon Sep 17 00:00:00 2001 From: Alexandru Nica Date: Fri, 31 Jul 2026 18:38:39 +0300 Subject: [PATCH 5/6] docs(solution): correct what deploy run does when the folder name is taken MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The doc said deployment fails if the folder already exists. It doesn't — Orchestrator collision-renames the new folder (`MySolution` → `MySolution 1`) and deploys there. That is exactly the trap in UV-15346: the reporter ran `deploy run` ~8 times against an existing folder holding assets, an IXP folder-deployment and a robot, got 8 new empty folders instead of an error, and runtime resolution then failed on the missing resources. Also states plainly that deploying into a pre-existing folder is not possible: the install API takes `solutionRootFolderName` — a name, not a folder key — so there is nothing to address the target with. Documents the two workarounds (deploy first then provision into the folder it created; or bind each piece with `or packages upload` + `or processes create`), and points readers at `Data.FolderPath` in the response rather than the name they passed. No CLI change: the gap is in the API contract, not in the wrapper. Co-Authored-By: Claude Opus 5 --- .../references/pack-and-deploy.md | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/skills/uipath-solution/references/pack-and-deploy.md b/skills/uipath-solution/references/pack-and-deploy.md index 5dd17c64dd..f5b0f901c7 100644 --- a/skills/uipath-solution/references/pack-and-deploy.md +++ b/skills/uipath-solution/references/pack-and-deploy.md @@ -122,7 +122,7 @@ Key options: | `-n, --name ` | Deployment name (required) | -- | | `--package-name ` | Published solution package name (required) | -- | | `--package-version ` | Package version to deploy (required) | -- | -| `--folder-name ` | New Orchestrator folder to create (required) | -- | +| `--folder-name ` | New Orchestrator folder to create (required). Always creates; a taken name is collision-renamed, never reused — see [`deploy run` Always Creates a New Folder](#deploy-run-always-creates-a-new-folder) | -- | | `--parent-folder-path ` | Parent folder under which the new folder is created | -- | | `--parent-folder-key ` | Parent folder key (GUID, alternative to `--parent-folder-path`) | -- | | `--config-file ` | Configuration file from `deploy config get` | -- | @@ -315,9 +315,30 @@ These are different commands with different destinations: | `solution publish` | Solution feed | For deployment via `deploy run` | | `solution upload` | Studio Web | For browser-based editing | -### `deploy run` Creates a New Folder +### `deploy run` Always Creates a New Folder -`--folder-name` specifies a folder to **create**, not an existing folder to deploy into. If the folder already exists, deployment will fail. Use `--parent-folder-path` to set the parent folder where the new folder is created. +`--folder-name` specifies a folder to **create**, not an existing folder to deploy into. Use `--parent-folder-path` to set the parent folder where the new folder is created. + +**Re-running with a name that is already taken does not fail and does not reuse the folder** — Orchestrator collision-renames the new one (`MySolution` → `MySolution 1`) and the deployment lands there. Repeated deploys therefore accumulate `MySolution 1`, `MySolution 2`, … The CLI reads the resolved folder back and reports where the deployment actually went, so always trust `Data.FolderPath` in the response over the name you passed. + +**There is no way to deploy into a pre-existing folder.** The install API takes `solutionRootFolderName` — a name, not a folder key — so the target cannot be addressed. This matters when the folder you want already holds hand-provisioned assets, an IXP folder-deployment, or an assigned robot: the deployment will land somewhere else and runtime resolution then fails on the missing resources. + +Two ways around it: + +```bash +# A. Let deploy create the folder FIRST, then provision into it. +uip solution deploy run --name my-deployment --package-name my-package \ + --package-version 1.0.0 --folder-name MySolution --output json +# → read Data.FolderPath / Data.FolderKey from the response, then create the +# assets, folder-deploy IXP, and assign the robot into THAT folder. + +# B. Skip solution deploy and bind each piece into the existing folder. +uip or packages upload ./piece.nupkg --output json +uip or processes create --package-key --package-version \ + --folder-key --output json +``` + +Option A is preferred — it keeps the solution deployment intact. Option B loses the solution grouping, so use it only when the folder's existing contents cannot be recreated. ### `--parent-folder-path` is the Parent From 0a086e107bc3b043ea1807a6353430a86eb40717 Mon Sep 17 00:00:00 2001 From: Alexandru Nica Date: Fri, 31 Jul 2026 19:26:29 +0300 Subject: [PATCH 6/6] docs(solution): show the rename signal deploy run now emits deploy run resolves the folder a deployment actually landed in and emits RequestedFolderName + FolderNote when it differs from --folder-name. Documents that shape so readers know the rename is now detectable rather than silent. Co-Authored-By: Claude Opus 5 --- .../uipath-solution/references/pack-and-deploy.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/skills/uipath-solution/references/pack-and-deploy.md b/skills/uipath-solution/references/pack-and-deploy.md index f5b0f901c7..a34a2002b7 100644 --- a/skills/uipath-solution/references/pack-and-deploy.md +++ b/skills/uipath-solution/references/pack-and-deploy.md @@ -319,7 +319,20 @@ These are different commands with different destinations: `--folder-name` specifies a folder to **create**, not an existing folder to deploy into. Use `--parent-folder-path` to set the parent folder where the new folder is created. -**Re-running with a name that is already taken does not fail and does not reuse the folder** — Orchestrator collision-renames the new one (`MySolution` → `MySolution 1`) and the deployment lands there. Repeated deploys therefore accumulate `MySolution 1`, `MySolution 2`, … The CLI reads the resolved folder back and reports where the deployment actually went, so always trust `Data.FolderPath` in the response over the name you passed. +**Re-running with a name that is already taken does not fail and does not reuse the folder** — Orchestrator collision-renames the new one (`MySolution` → `MySolution 1`) and the deployment lands there. Repeated deploys therefore accumulate `MySolution 1`, `MySolution 2`, … + +The CLI resolves the real folder and says so when it differs from what you asked for: + +```json +{ + "FolderName": "MySolution 4", + "FolderPath": "MySolution 4", + "RequestedFolderName": "MySolution", + "FolderNote": "Requested folder 'MySolution' already existed, so Orchestrator created 'MySolution 4' instead. ..." +} +``` + +`RequestedFolderName` and `FolderNote` appear **only** on a rename, so their presence is the signal to check. Always provision against `Data.FolderPath`, never against the name you passed. **There is no way to deploy into a pre-existing folder.** The install API takes `solutionRootFolderName` — a name, not a folder key — so the target cannot be addressed. This matters when the folder you want already holds hand-provisioned assets, an IXP folder-deployment, or an assigned robot: the deployment will land somewhere else and runtime resolution then fails on the missing resources.