Reviewing UiPath Code
Overview
UiPath workflows have unique failure modes: data extraction fragility, asset-retrieval nulls, email/notification gotchas, and silent failures in exception handlers. This skill identifies real bugs—not style—by focusing on failure paths and data flow integrity.
Core Anti-Patterns to Hunt
1. Send Email Gotchas
| Bug | Symptom | Fix |
|---|---|---|
SaveAsDraft="True" |
Emails stuck in drafts, never sent | Set SaveAsDraft="False" or use Send() activity |
| No recipient validation | Invalid emails cause transaction failures | Add email format regex check before sending |
| Asset retrieval not null-checked | Crash if Asset is empty/null | Add guard: If(Not String.IsNullOrEmpty(assetValue)) |
| Asset split without trim | Extra spaces break email parsing | Use .Split(...).Select(e => e.Trim()) |
| No retry on transient errors | One SMTP timeout fails entire batch | Wrap in Try/Catch with 3x retry logic |
Example fix pattern:
<!-- BEFORE (broken) -->
<ui:GetRobotAsset AssetName="Mails"> [mails] </ui:GetRobotAsset>
<Assign> [mailsArray = mails.Split(New Char() {","c})] </Assign>
<!-- AFTER (safe) -->
<If Condition="[Not String.IsNullOrEmpty(mails)]">
<Assign> [mailsArray = mails.Split(...).Where(Function(e) IsValidEmail(e)).ToArray()] </Assign>
<!-- handle empty case -->
</If>
2. Data Extraction Brittleness
| Bug | Symptom | Fix |
|---|---|---|
| No extraction failure check | Process continues with null fields | Validate each extracted field is not empty |
| Hardcoded field positions | Layout change breaks extraction | Use anchor-based or CV-based extraction |
| Missing fields cause downstream crash | SharePoint upload fails silently | Default missing fields to "N/A" or skip record |
| Multiple identical actions | Redundant clicks slow process, add failures | Consolidate to single action; check logs |
| No rollback on partial extraction | Half-complete records in SharePoint | Extract all → validate all → then store |
Pattern to verify:
- Each
NGetText/NGetOCRhas a following validation activity - Missing fields handled explicitly (not just passed as null)
- Extraction wrapped in Try/Catch with field-specific logging
3. Orchestrator Asset Risks
| Bug | Symptom | Fix |
|---|---|---|
| Asset path wrong or folder removed | GetRobotAsset silently returns empty |
Verify Asset path exists before workflow runs |
| No asset existence check | Crashes only in prod (not dev) | Use Try/Catch around GetRobotAsset, log failure |
| Sensitive values (passwords, keys) | Logged in plaintext | Never log asset values; mask in output |
| Stale asset pulled at runtime | Configuration change doesn't take effect | Don't cache asset; re-fetch per transaction |
4. Exception Handling Gaps
| Bug | Symptom | Fix |
|---|---|---|
| Empty catch blocks | Exception silently swallowed, no trace | Log exception message + stack trace in every Catch |
Catch too broad (catches Exception) |
Business errors treated as system errors | Catch BusinessRuleException separately from Exception |
| Finally block has no cleanup | Resources leak (connections, files, windows) | Explicit cleanup: close browser, release locks |
| No transaction status update on error | Queue item never marked failed | Set status (Success/BusinessException/SystemException) in all paths |
5. Logging Deficiencies
| Bug | Symptom | Fix |
|---|---|---|
| No log of extracted values | Can't debug extraction failures | Log each extracted field immediately after extraction |
| No log of email recipients | Can't audit who got notified | Log recipient, subject, send timestamp |
| Exception log missing context | Stack trace alone doesn't point to cause | Include: TransactionID, input values, current state |
| No timing log | Performance bottlenecks invisible | Log start/end time for slow activities (web scrape, AI call) |
Audit Checklist
Email/Notification Flow
- Email recipient validated before send (format check, not empty)
- Recipient fetched from approved Orchestrator Asset, not hardcoded
- Recipient list trimmed/deduplicated
- SendMailConnections:
SaveAsDraft="False"(or equivalent send) - Retry logic for transient mail failures
- Log: recipient, subject, success/failure, timestamp
- Email body includes relevant transaction data (not generic)
- Condition for sending is explicit and logged (not implicit)
Data Extraction
- Each extracted field logged immediately after NGetText/NGetOCR
- Null/empty fields handled: either reject, default, or skip
- Extraction failures wrapped in Try/Catch
- No hardcoded field positions; use anchors/CV/selectors
- Validation logic before downstream activities (upload, send)
- Extraction order matches expected data flow (don't extract out of order)
Orchestrator Integration
- All GetRobotAsset calls guarded by null check
- Asset paths logged at start for debugging
- Config values used (not hardcoded URLs, paths, recipients)
- No sensitive data in logs
Exception Handling
- No empty Catch blocks
- Business exceptions (
BusinessRuleException) handled separately - System exceptions logged with full context: TransactionID, input, state
- Transaction status updated in all paths (Success/BRE/System)
- Finally block has cleanup code (if needed)
Logging & Audit Trail
- Transaction start logged with ID
- Each major step logged (extract → validate → store)
- Extracted values logged (not sensitive ones)
- Email sent/failed logged per recipient
- Any error includes: error message, source, transaction ID
Common Mistakes
| Mistake | Why It Breaks | Example |
|---|---|---|
| "This email validation is too strict, skip it to save time" | One invalid email stops all notifications | Validate every recipient, log failures per-email |
| "We'll retry emails offline" | Never happens; broken emails stay broken | Retry logic must be inline |
| "Empty fields are fine, downstream will handle it" | Downstream crashes or produces garbage records | Validate at extraction time |
| "Logs clutter the output, let errors speak for themselves" | Errors are silent; untraceable in prod | Log everything; filter output locally, not in code |
| "SaveAsDraft is fine for testing" | Forgotten before deploy; emails never reach users | Always deploy with SaveAsDraft=False |
Red Flags — Stop and Review
- Any
SaveAsDraft="True"in email activities - Email recipient list not validated/trimmed
-
GetRobotAssetresult used without null check - Extract field, don't check if null, use downstream
- Catch block with no LogMessage
- Identical Click activities in sequence (redundant)
- Email body is generic/hardcoded (doesn't include order data)
- No retry logic on Mail Send activity
- Asset path hardcoded (should be in Config)
- No TransactionID in error logs
Implementation Pattern
For each activity that touches external systems:
- Try — Invoke activity (send email, extract data, upload file)
- Catch BusinessRuleException — Log, set status, continue
- Catch SystemException — Log with context, retry or end
- Catch generic Exception — Treat as system error (shouldn't happen)
- Finally — Cleanup (close connections, etc.)
For each extracted field:
<NGetText> [fieldValue] </NGetText>
<ui:LogMessage Message="["Extracted [fieldName]: " + fieldValue]" />
<If Condition="[String.IsNullOrEmpty(fieldValue)]">
<!-- Handle missing: throw BRE, default, or skip -->
</If>
Real-World Impact
A single forgotten SaveAsDraft="True" or missing email validation can:
- Block all notifications in production
- Leave stakeholders unaware of high-priority opportunities
- Mask extraction failures (silently create incomplete records)
- Create audit/compliance gaps (no record of who was notified)
Testing email workflows end-to-end catches these. Spot-checking code before merge prevents them entirely.