Skip to content

cherry-pick(release/v1.202): wire per-outcome handles, drop the forced Decision node after HITL (#3180) - #3268

Closed
dushyant-uipath wants to merge 1 commit into
release/v1.202from
cp/hitl-outcome-handles-v1.202
Closed

dushyant-uipath wants to merge 1 commit into
release/v1.202from
cp/hitl-outcome-handles-v1.202

Conversation

@dushyant-uipath

@dushyant-uipath dushyant-uipath commented Sep 12, 2026 •

Copy link
Copy Markdown
Collaborator

Cherry-pick of #3180 (main, merge commit 81bb535) onto release/v1.202. Rebased 2026-09-17 onto current release/v1.202 tip (0c48173..31d5b7c), which already carries the separately-merged #3277 and #3280 cherry-picks. No conflicts on this rebase.

One conflict on the original cherry-pick (skills/uipath-maestro-flow/references/author/plugins/hitl/impl.md, context drift around the AppTask rules bullet list), resolved by keeping the incoming rules verbatim.

Real run proof the fix works

  • Built a real Flow today (2026-09-17) with a HITL QuickForm node (Approve/Reject outcomes) and a HITL App-based node, following this branch's docs exactly, then ran the actual product CLI: uip maestro flow validate returns {"Result": "Success", "Data": {"Status": "Valid"}} with the outcomes wired as outcome-approve / outcome-reject (not completed), and the App-based node's static completed port left untouched.
  • skill-hitl-e2e-invoice-approval-greenfield (e2e_01) run locally through coder-eval: SUCCESS, score 1.000, 7/7 criteria, 482.7s.
  • skill-hitl-e2e-expense-approval-brownfield (e2e_05) run locally through coder-eval: SUCCESS, score 1.000, 4/4 criteria, 332.3s.

Test plan

  • python3 -m pytest on the two directly affected test files: 19/19 passing on this branch.
  • Real uip maestro flow validate run against a hand-built flow using this branch's documented wiring: passes.
  • Two real end-to-end coder-eval task runs (e2e_01, e2e_05) against the agent following this branch's docs: both SUCCESS.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @dushyant-uipath's task in 3m 38s —— View job


PR Review: Cherry-pick HITL per-outcome handles onto release/v1.202

  • Read review criteria and project conventions
  • Analyze full diff against release/v1.202
  • Review skill documentation changes
  • Review test changes
  • Check for cross-skill dependencies, broken links, secrets
  • Post review findings

Summary

Cherry-pick of #3180 onto release/v1.202. Changes the HITL QuickForm wiring model from a single completed port to one outcome-<outcome.id> port per outcome, removes the forced Decision node pattern after HITL, and updates both skill docs and test task YAMLs/checkers accordingly. One conflict in hitl/impl.md resolved by keeping incoming rules verbatim.

Change-by-Change Review

1. skills/uipath-human-in-the-loop/SKILL.md

OK — Critical Rules #2 and #3 updated to reflect per-outcome wiring for QuickForm and static completed for AppTask. Clear distinction between the two node types. CLI section updated consistently.

2. skills/uipath-human-in-the-loop/references/hitl-node-apptask.md

OK — Correctly clarifies that AppTask uses static completed, unlike QuickForm's outcome-derived ports. New warning against adding inputs.schema to app-based nodes.

3. skills/uipath-human-in-the-loop/references/hitl-node-quickform.md

OK — Edge Wiring section updated with per-outcome examples (outcome-approve, outcome-reject). New outcome id row added to the field reference table with conversion rules. Warning about outcome-completed placeholder is clear.

4. skills/uipath-maestro-flow/references/author/plugins/hitl/impl.md

OK — Option 1 ports rule rewritten for per-outcome semantics. Option 2 AppTask template correctly removes the inputs.schema block that was previously included (would wrongly flip the port model). New rules about priority derivation and no-inputs.schema for AppTask. Common pattern and debug table updated.

5. skills/uipath-maestro-flow/references/author/plugins/hitl/planning.md

OK — Ports table, wiring patterns, and topology examples all updated. "Do not insert a Decision node after a HITL node" anti-pattern is explicit and well-motivated.

6. skills/uipath-maestro-flow/references/diagnose/ (CAPABILITY.md, failure-modes.md)

OK — Anchor renamed from hitl-completed-port-unwired → hitl-outcome-port-unwired. All internal links verified: 3 references to the new anchor, 0 dangling references to the old one.

7. Other maestro-flow references (brownfield.md, editing-operations.md, planning-arch.md, cli-commands.md)

OK — Single-line updates replacing completed with outcome-<outcome.id> terminology. All consistent.

8. tests/tasks/uipath-maestro-flow/_shared/check_simulated_hitl.py

OK — New assert_outcome_wiring() and check_outcome_wiring() functions. Logic is sound: checks each outcome has a non-empty id, rejects outcome-completed when real outcomes exist, verifies every outcome port is wired. The check_expense() function now delegates to assert_outcome_wiring() instead of inline logic.

9. tests/tasks/uipath-maestro-flow/e2e/check_devcon_expense_approval.py

Severity: Low — Outcome-wiring logic is correct, but two minor items:

  • Line 149: Success message still says "wires completed" — should say "wires outcome ports" or similar to match the new semantics. Cosmetic only; does not affect test pass/fail.
  • Duplication: The outcome-wiring logic (lines 120–138) is a verbatim copy of assert_outcome_wiring() in check_simulated_hitl.py. The devcon checker already imports from _shared (via flow_check), so it could call the shared function instead. Not a blocker since both copies are introduced in the same commit and will stay in sync, but worth noting.

10. tests/tasks/uipath-maestro-flow/e2e/test_check_devcon_expense_approval.py

OK — Four new negative tests (test_rejects_dangling_outcome_port, test_rejects_outcome_completed_with_real_outcomes, test_accepts_mixed_case_outcome_id, test_rejects_outcome_without_id) provide excellent coverage of the new wiring rules. The _flow_doc helper updated with outcome_ports parameter for flexible test setup.

11. tests/tasks/uipath-maestro-flow/_shared/test_batch2_alignment.py

OK — Test fixture updated: outcomes now have id fields, edges wire outcome-approve/outcome-reject instead of completed.

12. HITL test task YAMLs (e2e_01 through e2e_06, quality_04/05/07/08/09/10/11)

OK — All initial_prompt wiring instructions updated from completed to per-outcome terminology. Success criteria switched from file_contains (checking for substring "completed") to run_command calling check_simulated_hitl.py outcome-wiring — a significantly more precise check. Test descriptions and TEST_PLAN.md updated consistently.

13. tests/tasks/uipath-human-in-the-loop/e2e_01_invoice_approval_greenfield.yaml

OK — Prompt simplified to mock extraction/posting as Script nodes (was triggering live connector discovery that blew through timeouts). Timeout reduced from 2400→1200s with detailed rationale in comments. Confirmed passing locally per the comment.

14. Maestro-flow HITL test YAMLs (smoke_02, smoke_03, quality_01-04)

OK — Prompts and criteria updated consistently. The smoke_02 description accurately reflects the new "per-outcome port" semantics. Comments explaining the check mechanism updated to reference check_simulated_hitl.py's assert_outcome_wiring.

15. tests/tasks/uipath-maestro-flow/interactive/expense_approval_simulated/expense_approval_simulated.yaml

OK — Description and criteria description updated to reflect outcome-port wiring.

What's Missing

  • Stale success message (check_devcon_expense_approval.py:149): Says "wires completed" but the check now validates per-outcome wiring. Cosmetic — does not affect correctness. Fix this →
  • Duplicated outcome-wiring logic in check_devcon_expense_approval.py (lines 120–138) vs check_simulated_hitl.py's assert_outcome_wiring(). Could import the shared function to stay DRY. Not urgent since both are introduced in the same commit.

Area Ratings

Area Status Notes
Frontmatter OK No frontmatter changes in this PR
E2E Tests OK New negative tests for outcome-wiring edge cases; all tasks updated
Skill Body OK Critical Rules, wiring patterns, and anti-patterns all updated consistently
References & Assets OK All anchor links verified; no broken cross-references
Repo Hygiene OK No secrets, no cross-skill structural dependencies, no unrelated changes

Issues for Manual Review

  • Domain accuracy: The claim that outcome-completed is a zero-outcome placeholder that disappears once real outcomes exist is a UiPath platform behavior — cannot be verified from the docs alone. The PR author's commit message and test evidence suggest this is correct.
  • Conflict resolution: The PR states one conflict in impl.md was resolved by keeping incoming rules verbatim. The resulting file reads cleanly with no merge artifacts.

Conclusion

Clean cherry-pick. The conceptual shift from single completed port to per-outcome outcome-<id> ports is applied consistently across all 37 changed files — skill docs, test tasks, and test checkers are all aligned. The new assert_outcome_wiring checker is well-designed with good negative test coverage. Two low-severity items noted (stale success message, duplicated logic) — neither blocks merge.


@github-actions

github-actions Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @dushyant-uipath's task in 4m 47s —— View job


Coder-eval task lint (advisory)

21 task YAMLs changed (all modifications, no new tasks); verdicts: 0 Critical, 0 High, 0 Medium, 4 Low, 17 OK.

Rubric: .claude/commands/lint-task.md. This check is advisory and never blocks merge.

Evidence of passing run

❌ High — PR body claims python3 -m pytest passes on two Python test files (19/19), but does not claim the modified coder-eval task YAMLs were run and passed. Note: e2e_01_invoice_approval_greenfield.yaml line 31 embeds a passing-run claim in a YAML comment ("Confirmed locally (coder-eval 0.12.0, claude-sonnet-4-6): SUCCESS, score 1.000, 7/7 criteria"), but this is inside the file, not in the PR description. Please consider adding a line to the PR body like: Ran <task-id> locally and it passed.

Per-task lint

tests/tasks/uipath-human-in-the-loop/e2e_01_invoice_approval_greenfield.yaml — verdict: OK

Changes strengthen coverage: prompt mocks connectors as Script nodes instead of real SharePoint/SAP (reduces timeout from 2400→1200), criteria upgraded from flow_contains ... 'completed' to check_simulated_hitl.py outcome-wiring (semantic check). No issues.

tests/tasks/uipath-human-in-the-loop/e2e_02_ai_escalation_brownfield.yaml — verdict: OK

tests/tasks/uipath-human-in-the-loop/e2e_03_gdpr_compliance_greenfield.yaml — verdict: OK

tests/tasks/uipath-human-in-the-loop/e2e_04_multi_hitl_brownfield.yaml — verdict: OK

tests/tasks/uipath-human-in-the-loop/e2e_05_expense_approval_brownfield.yaml — verdict: OK

tests/tasks/uipath-human-in-the-loop/e2e_06_invoice_approval_greenfield_simple.yaml — verdict: OK

tests/tasks/uipath-human-in-the-loop/quality_04_all_handles.yaml — verdict: OK

tests/tasks/uipath-human-in-the-loop/quality_05_priority_and_timeout.yaml — verdict: OK

tests/tasks/uipath-human-in-the-loop/quality_07_runtime_vars.yaml — verdict: OK

tests/tasks/uipath-human-in-the-loop/quality_08_variable_binding_fieldid.yaml — verdict: OK

tests/tasks/uipath-human-in-the-loop/quality_09_dev_mistake_wrong_type.yaml — verdict: OK

tests/tasks/uipath-human-in-the-loop/quality_10_dev_mistake_binding_direction.yaml — verdict: OK

tests/tasks/uipath-human-in-the-loop/quality_11_inout_field_access.yaml — verdict: OK

All 13 uipath-human-in-the-loop tasks: prompt wording updated from "completed handle" to "outcome port(s) / every outcome needs its own wired port"; criteria upgraded from weak file_contains/flow_contains ... 'completed' string match to semantic check_simulated_hitl.py outcome-wiring. All changes strengthen coverage. No issues introduced.

tests/tasks/uipath-maestro-flow/e2e/devcon_expense_approval.yaml — verdict: OK

Description and criteria text updated. Rationale for validate-only documented: "inline HITL nodes block on human review and cannot be flow debug-ged headlessly." No issues.

tests/tasks/uipath-maestro-flow/hitl/quality_01_schema_design.yaml — verdict: Low (theme-captured; see Theme 1)

tests/tasks/uipath-maestro-flow/hitl/quality_02_result_downstream.yaml — verdict: Low (theme-captured; see Theme 1)

tests/tasks/uipath-maestro-flow/hitl/quality_03_boolean_decision.yaml — verdict: Low (theme-captured; see Theme 1)

tests/tasks/uipath-maestro-flow/hitl/quality_04_brownfield_insert.yaml — verdict: Low (theme-captured; see Theme 1)

tests/tasks/uipath-maestro-flow/hitl/smoke_02_completed_port_wired.yaml — verdict: OK

Description and criteria substantially improved: now tests per-outcome port wiring instead of generic "completed" string. Smoke tier — validate-only exempt.

tests/tasks/uipath-maestro-flow/hitl/smoke_03_multi_outcome_routing.yaml — verdict: OK

Prompt updated to explicit per-outcome wiring. No issues.

tests/tasks/uipath-maestro-flow/interactive/expense_approval_simulated/expense_approval_simulated.yaml — verdict: OK

Comment and description text updated. Validate-only rationale present ("inline HITL nodes block on human review"). No issues.

Within-PR duplicates

No duplicate clusters detected — all 21 files are modifications of existing tasks; no new task_id values added.

Themes

  • Theme 1 — Validate-only flow tests (Medium, pre-existing): 4 maestro-flow integration-tier HITL tasks (quality_01 through quality_04) have no flow debug criterion and no explicit validate-only rationale in their description field. This is pre-existing (not introduced by this PR) and inherent to HITL testing — inline HITL nodes block on human review and can't be debugged headlessly. Consider adding a brief rationale to each task's description (e.g. "Validate-only: inline HITL nodes cannot be flow-debugged headlessly") to earn the description-rationale carve-out and silence future lint.

Conclusion

⚠ 4 task(s) have pre-existing issues (max severity Medium, downgraded to Low via theme). All changes introduced by this PR are improvements — criteria upgraded from weak string matches to semantic outcome-wiring checks. Evidence of passing coder-eval run missing from PR body (High). Advisory only — not blocking merge.


@dushyant-uipath
dushyant-uipath force-pushed the cp/hitl-outcome-handles-v1.202 branch 2 times, most recently from 9e1205c to e665e91 Compare September 18, 2026 13:00
…ecision node after HITL (#3180)

* fix(uipath-maestro-flow): wire per-outcome handles, drop the forced Decision node after HITL

Two confirmed bugs in the HITL authoring guidance, verified against
flow-workbench source with the Flow skill team:

1. impl.md and planning.md only ever documented one output port,
   outcome-completed, regardless of how many outcomes a QuickForm schema
   declares. outcome-completed is actually the zero-outcome placeholder only
   — it disappears the instant the schema has any real outcome (even the
   default Submit gets outcome-submit). Agents following the old docs left
   every real outcome's handle dangling: invisible in canvas, but the runtime
   still executed the flow as if it were connected.
2. Both docs canonicalized inserting a Decision node after every HITL node to
   re-branch on $vars.{nodeId}.status. That's redundant: each outcome's own
   handle has showButton: true specifically so it can branch directly.

Rewrote the wiring rules, port tables, and example flows in both docs to use
outcome-<outcome.id> per outcome and branch directly off those handles.

Also fixed three uipath-maestro-flow test files that graded the old (wrong)
behavior as correct — they would have started failing agents that follow the
corrected skill, not because of a regression but because the grading itself
still expected the generic completed port:
- smoke_02_completed_port_wired.yaml asserted the bare substring "completed"
  against a 2-outcome (Approve/Reject) schema, which the corrected skill
  never emits. Now asserts outcome-approve and outcome-reject are both wired
  and outcome-completed is absent; updated the prompt to ask for both outcome
  branches explicitly.
- check_simulated_hitl.py and check_devcon_expense_approval.py both
  hard-required a completed/outcome-completed edge. Now derive the required
  outcome-<id> ports from the flow's own parsed schema.outcomes[] and fail if
  any is unwired or if outcome-completed appears alongside real outcomes.
  Added two negative tests to test_check_devcon_expense_approval.py covering
  a dangling outcome port and a wired outcome-completed placeholder.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(uipath-maestro-flow): fall back to outcome.name for id, fix check ordering

CI caught two real bugs in the outcome-<id> checker changes:

1. check_simulated_hitl.py and check_devcon_expense_approval.py required an
   explicit outcome.id, but test_batch2_alignment.py's pre-existing fixture
   uses outcomes with only a name ({"name": "Approve"}), which is a valid
   shape per hitl-node-quickform.md's own convention of defaulting id to the
   lowercased name. Both checkers now fall back to name when id is absent.
   Also updated that fixture's edges from the legacy bare "completed" port to
   outcome-approve/outcome-reject, since it wires a 2-outcome node and bare
   "completed" is the same stale assumption this PR removes elsewhere.

2. The "wired outcome-completed alongside real outcomes" check ran after the
   "missing ports" check, so it could never actually fire: if outcome-completed
   is wired instead of the real ports, the real ports are by definition also
   missing, and that check always won the race. Reordered so the more
   specific, more useful message fires first. Updated
   test_rejects_outcome_completed_with_real_outcomes's expected message to
   match.

Confirmed separately that PR #3180's "Run skill smoke tests" failure is
unrelated: skill-flow-hitl-smoke-completed-port itself passed
(status=SUCCESS, score=1.00); the suite dropped below the 95% pass-rate gate
because of 3 failing skill-flow-datafabric-smoke-* tasks, pre-existing and
outside this change's scope.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(uipath-maestro-flow, uipath-human-in-the-loop): finish the outcome-port fix Rocky's review found incomplete

Rocky Madden reviewed the original fix (request changes, verified against
flow-workbench@develop source directly) and found the fix touched 2 of 8
uipath-maestro-flow files that teach the completed port, left the sibling
uipath-human-in-the-loop skill teaching the opposite as a Critical Rule while
marking it stable in the same PR, missed ~14 affected task YAMLs in both
skills' own test suites, and got the outcome-id derivation wrong in the
checkers (falling back to a lowercased name, which the product does not do).

Verified the id-derivation question with the Flow session against source
(build-handle-customization.ts filters outcomes by explicit id, no name
fallback; the editor always assigns one at creation) before touching the
checkers again.

Docs, uipath-maestro-flow (6 more files):
- editing-operations.md, planning-arch.md, brownfield.md, cli-commands.md:
  wiring instructions now say outcome-<outcome.id> per outcome.
- diagnose/failure-modes.md: renamed the "HITL completed port unwired"
  section to "HITL outcome port unwired", rewrote symptom/cause/fix, updated
  the anchor and its two referrers (the table row and CAPABILITY.md).
- impl.md Option 2 (coded-action-app): removed the inputs.schema.outcomes
  block from the example — Rocky found it flips the node's real static
  completed port to an outcome-derived one, which the product does not do
  for this node type. Documented that Option 2 keeps a static completed
  port regardless of the app's own outcomes.

Docs, uipath-human-in-the-loop (the sibling skill impl.md delegates node
writing to):
- SKILL.md Critical Rule 2 rewritten: QuickForm wires outcome-<outcome.id>
  per outcome; App-based keeps a static completed port and must not carry an
  inputs.schema block. Rule 3 and the CLI-add step no longer assert a
  literal completed handle.
- hitl-node-quickform.md Edge Wiring section rewritten to the per-outcome
  model; the static handleConfiguration JSON block is untouched per Rocky's
  own line citations.
- hitl-node-apptask.md's two "identical to QuickForm" claims corrected —
  App-based is the one that stays static now, not QuickForm.

Checkers (reverted the wrong id fallback):
- check_simulated_hitl.py, check_devcon_expense_approval.py: outcome ids
  must be explicit (outcomes[].id) with no name-derived fallback, matching
  the confirmed product behavior. Also confirmed check ordering already
  flags a wired outcome-completed placeholder before the generic
  missing-ports message.
- test_batch2_alignment.py: its fixture used name-only outcomes
  ({"name": "Approve"}), which the checker fallback used to paper over; gave
  it explicit ids instead.

Tests (the ~14+ files Rocky flagged in both skills' suites):
- uipath-maestro-flow/hitl/quality_01..04, smoke_03: prompts and criteria
  updated. quality_01 drops its Decision node (redundant once outcome ports
  branch directly); quality_03, smoke_03, and human-in-the-loop's
  quality_10 keep their Decision nodes since those branch on a boolean
  field value, not on which outcome was clicked — a genuinely different
  case the corrected planning.md calls out as still valid.
  quality_04_brownfield_insert.yaml's criterion always scored 0 against the
  corrected skill (it asserted the literal substring "completed" against a
  2-outcome schema); replaced with a check for both real outcome ports.
- uipath-human-in-the-loop/e2e_01..06, quality_04/05/07/08/09/10/11:
  prompts and criteria updated the same way. e2e_02 and e2e_04 used
  file_contains (no regex support) and were converted to run_command +
  flow_contains.py --regex to express "a real outcome port, not the
  placeholder."
  e2e_07 (App-based, skip: true) is unchanged — it already correctly wires
  a static completed handle.
- TEST_PLAN.md: invariant table and per-task summaries updated to match.
- uipath-maestro-flow/interactive/expense_approval_simulated.yaml and
  e2e/devcon_expense_approval.yaml: only their descriptive comments said
  "completed" — the actual grading already runs through the corrected
  checker scripts above, so behavior did not change, only the prose.

Not fixed here: the description of the failure mode in the original PR
overstated it ("ran the flow as if connected"). Rocky's source read
(hitl-edge-reconciliation.ts) says a lone placeholder edge remaps onto the
primary outcome and is cosmetic; the real breakage is (a) multi-outcome
schemas where only the primary gets remapped and the rest dangle, and (b) a
placeholder edge duplicating a real outcome-<id> edge, which the BPMN engine
takes unconditionally alongside the conditioned one and double-executes that
branch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(flow): restore scaffold_multinode run_limits dropped by #2534 (#2572)

The criteria migration in #2534 replaced the file's final criterion block
and accidentally swallowed the trailing run_limits block, so the task fell
back to the experiment defaults (max_turns 200 / task_timeout 1200) instead
of its 40-turn / 900s limits. Restore the exact prior block. Config-only
revert to the pre-#2534 state; no criteria changed.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(test): raise e2e_01 timeout to 3600, 2400 confirmed insufficient

2026-09-11 run 2026-09-11_04-17-20: ERROR, "Agent turn timed out after
2400s" (iteration 1, 119 assistant turns, max_turns_exhausted False). The
iteration log shows continuous real work for the full 2400s, not a loop:
live SharePoint/SAP connection discovery, an IXP taxonomy lookup for
invoice extraction, and registry lookups for every node type, each a real
network round trip, with the agent still on setup work at the 25-minute
mark. Raised both task_timeout and turn_timeout to 3600, matching the
established heavy-single-turn budget already used elsewhere in the repo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(test): scale e2e_01 down to focus on HITL wiring, not connector discovery

skill-hitl-e2e-invoice-approval-greenfield kept timing out for a reason
neither #3196 (1200s -> 2400s) nor the 2400s -> 3600s bump addressed at the
root: the prompt required real SharePoint extraction and real SAP posting,
so every run did live connector/registry/IXP-taxonomy discovery before ever
touching the HITL node the test is meant to grade. 2026-09-11 run
2026-09-11_04-17-20 spent all 2400s and 119 assistant turns on exactly that
discovery work, still mid-setup.

Mocked both integration points as Script nodes instead of raising the
timeout again. The test exists to exercise HITL schema design and
outcome-port wiring, not connector discovery, and the prompt still requires
the agent to recognize the write-back + approval gate signal on its own.
Dropped task_timeout/turn_timeout back to 1200/1200, matching the sibling
e2e brownfield tasks that already mock their upstream/downstream the same
way, and confirmed locally: coder-eval 0.12.0, claude-sonnet-4-6, SUCCESS,
score 1.000, 7/7 criteria, 482.7s, 34 assistant turns. Raised expected_turns
(14 -> 34) and max_turns (40 -> 50) to match that real usage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(uipath-maestro-flow, uipath-human-in-the-loop): address remaining inline review comments

Went back through every inline comment on the review (not just the summary)
and found several the earlier round-2/round-3 fixes had not actually
addressed:

- impl.md:69/182 and planning.md:55 overclaimed "outcome-completed never
  reappears" / "is not a real outcome port". It is an ordinary string, not a
  reserved one: an outcome whose own id is literally "completed" produces
  the identical port. Reworded to the accurate claim (zero-outcome
  placeholder, or a real outcome id'd "completed") in both files, plus the
  impl.md debug-table row.
- check_simulated_hitl.py's new outcome-id/wiring block made outcomes
  mandatory, contradicting the function's own earlier gate that explicitly
  accepts a boolean-decision field with no outcomes. Extracted the shared
  logic into assert_outcome_wiring() and gated it on outcomes being
  non-empty; the zero-outcome path now correctly falls back to requiring
  the completed placeholder, matching pre-existing behavior.
- Same file (and check_devcon_expense_approval.py) had the outcome-completed
  check running after the missing-ports check, so it could never fire (a
  wired outcome-completed always looks like every real port is missing).
  Reordered in both. Caught by re-running the unit tests locally before
  pushing, not just by inspection.
- Added check_outcome_wiring, a generic node-scoped check (handles 1+ HITL
  nodes) built on the same assert_outcome_wiring(), and pointed smoke_02 and
  7 other task YAMLs at it instead of a flow_contains.py --absent-regex.
  That regex was file-scoped: it would false-fail the moment a flow gained
  an unrelated node with a legitimate completed port (an Option 2 HITL
  node), and it pinned outcome-approve/outcome-reject as guessed literals
  rather than deriving them from the schema.
- Added the missing outcome-id conversion rule to hitl-node-quickform.md's
  table (same lowercase-name convention as field id), so those literals are
  now backed by a documented rule instead of one example.
- Deduplicated the repeated 6-line comment block across check_simulated_hitl.py,
  check_devcon_expense_approval.py, and smoke_02 into one full statement in
  check_simulated_hitl.py plus short pointers, and dropped the
  "confirmed against ... 2026-09-10" narration in favor of a file a reader
  can open.
- Added two tests to test_check_devcon_expense_approval.py: a mixed-case
  outcome id (the handle must use it verbatim, never lowercased) and an
  outcome with no id (must fail loudly, not get silently dropped).

Ran the full local test suite before pushing: 19/19 in the two directly
affected test files, no HITL/outcome-related failures anywhere else in
uipath-maestro-flow (133 pre-existing failures elsewhere are unrelated —
test_criterion_budgets.py and a Data Fabric connector suite, matching CI's
already-known unrelated flakiness).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Nikhil Maryala <87343340+nikhil-maryala@users.noreply.github.com>
(cherry picked from commit 81bb535)
@dushyant-uipath
dushyant-uipath force-pushed the cp/hitl-outcome-handles-v1.202 branch from e665e91 to 2d98151 Compare September 21, 2026 07:52
@dushyant-uipath

Copy link
Copy Markdown
Collaborator Author

Closing. The fix (#3180) is already present in release/v1.203, which branched off main after #3180 merged, so no cherry-pick was needed there. Skipping this 1.202 backport rather than fighting the release-branch CI infra issue (org secrets not reaching the smoke-test job's auth step, needs org-admin access to diagnose, tracked separately). 1.203.0 is still in preview on npm (highest published: 1.203.0-preview.889; latest stable is 1.202.1) as of 2026-09-21.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant