Skip to content

AAP-12516 [option 2] Handle nested workflow artifacts via root node ancestor_artifacts#16381

Open
AlanCoding wants to merge 7 commits intoansible:develfrom
AlanCoding:nested_precedence2
Open

AAP-12516 [option 2] Handle nested workflow artifacts via root node ancestor_artifacts#16381
AlanCoding wants to merge 7 commits intoansible:develfrom
AlanCoding:nested_precedence2

Conversation

@AlanCoding
Copy link
Copy Markdown
Member

@AlanCoding AlanCoding commented Apr 1, 2026

SUMMARY

I didn't love

#16379

because it changed an existing story in an unintended way.

This adds an additional test, and preserves the behavior in that prior case.

This is also more philosophically satisfying. As you are processing a root node, you can not easily grab the node that generated that workflow in the parent workflow. So when that parent workflow's node generates the nested workflow, this has it apply ancestor_artifacts to the root nodes of the child. This is much more intuitively consistent. It shifts the logic location a little bit, but overall much more satisfying.

ISSUE TYPE
  • Bug, Docs Fix or other nominal change
COMPONENT NAME
  • API

Note

Medium Risk
Changes how set_stats artifacts are propagated into nested workflows by moving propagation to runtime seeding of child workflow root nodes, which can affect variable precedence and workflow/job behavior. Updates touch workflow scheduling/job spawning paths, so regressions could impact workflow launches and extra_vars merging.

Overview
Fixes nested workflow artifact precedence by stopping parent workflow artifacts from being merged into a child workflow’s extra_vars and instead seeding them onto the child workflow’s root nodes’ ancestor_artifacts so they propagate normally.

Adds WorkflowJob.seed_root_ancestor_artifacts() and calls it from the task manager when spawning a nested workflow, and updates WorkflowJobNode.get_job_kwargs() to initialize from pre-seeded artifacts (excluding job_slice) and only apply artifacts into extra_vars for JobTemplate nodes.

Adds a small set_stats.yml playbook plus live tests covering (1) nested workflow set_stats override behavior and (2) workflow-level extra_vars continuing to override artifacts.

Written by Cursor Bugbot for commit 1c5d0f2. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed nested workflow artifact propagation so seeded root artifacts correctly propagate through nested workflows; child workflows no longer inappropriately receive parent artifacts via extra variables and workflow-level extra_vars keep precedence.
  • Tests
    • Added live tests and a test playbook to validate artifact propagation and extra_vars precedence across nested and single-level workflows.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1a160467-05bb-4608-9507-951cc8cb9a10

📥 Commits

Reviewing files that changed from the base of the PR and between 405c466 and 1c5d0f2.

📒 Files selected for processing (1)
  • awx/main/models/workflow.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • awx/main/models/workflow.py

📝 Walkthrough

Walkthrough

Ancestor-artifact propagation for nested workflows was changed: root nodes of nested workflow jobs are now seeded with ancestor_artifacts via a new method; WorkflowJobNode.get_job_kwargs() starts aggregation from pre-seeded ancestor_artifacts and only merges artifacts into extra_vars when the unified job template is a JobTemplate.

Changes

Cohort / File(s) Summary
Workflow model changes
awx/main/models/workflow.py
WorkflowJobNode.get_job_kwargs() now initializes aggregation from existing self.ancestor_artifacts (excluding internal job_slice), merges ancestor artifacts into extra_vars only when the unified job template is a JobTemplate (no merge for WorkflowJobTemplate), and a new WorkflowJob.seed_root_ancestor_artifacts(artifacts) method seeds root workflow nodes with ancestor artifacts.
Scheduler spawn logic
awx/main/scheduler/task_manager.py
After creating/saving a spawned unified job, scheduler conditionally calls job.seed_root_ancestor_artifacts(spawn_node.ancestor_artifacts) when spawn_node.ancestor_artifacts exists and the spawned unified job template is a WorkflowJobTemplate.
Tests and test data
awx/main/tests/data/projects/debug/set_stats.yml, awx/main/tests/live/tests/test_nested_workflow_artifacts.py
Added a playbook using set_stats and two live tests that verify artifact precedence across nested workflows and that workflow-level extra_vars override job-generated artifacts.

Sequence Diagram

sequenceDiagram
    participant ParentWF as Parent Workflow Job
    participant Scheduler as Task Manager
    participant SpawnedJob as Spawned Unified Job
    participant NestedRoot as Root Node of Nested Workflow

    ParentWF->>Scheduler: request spawn_node (includes ancestor_artifacts)
    Scheduler->>SpawnedJob: create_unified_job(...)
    Scheduler->>SpawnedJob: save job
    alt spawn_node.ancestor_artifacts && unified_job_template is WorkflowJobTemplate
        Scheduler->>SpawnedJob: seed_root_ancestor_artifacts(artifacts)
        SpawnedJob->>NestedRoot: set ancestor_artifacts on root nodes
    end
    Scheduler->>Scheduler: continue launch checks and signaling
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: handling nested workflow artifacts via root node ancestor_artifacts as an alternative approach (option 2) to address ticket AAP-12516.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
awx/main/scheduler/task_manager.py (1)

244-245: Prefer explicit nested-workflow seeding over truthiness-gated seeding.

Using truthiness on ancestor_artifacts means {} skips this path. Making the nested-workflow call unconditional (with a safe default) keeps behavior explicit and less fragile.

Proposed refactor
-                    if spawn_node.ancestor_artifacts and isinstance(spawn_node.unified_job_template, WorkflowJobTemplate):
-                        job.seed_root_ancestor_artifacts(spawn_node.ancestor_artifacts)
+                    if isinstance(spawn_node.unified_job_template, WorkflowJobTemplate):
+                        job.seed_root_ancestor_artifacts(spawn_node.ancestor_artifacts or {})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@awx/main/scheduler/task_manager.py` around lines 244 - 245, Replace the
truthiness check on spawn_node.ancestor_artifacts with an explicit call that
always seeds a safe default: keep the WorkflowJobTemplate type check
(isinstance(spawn_node.unified_job_template, WorkflowJobTemplate)) and
unconditionally call
job.seed_root_ancestor_artifacts(spawn_node.ancestor_artifacts or {}) so empty
dicts no longer skip seeding but None still results in an empty dict being
passed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@awx/main/scheduler/task_manager.py`:
- Around line 244-245: Replace the truthiness check on
spawn_node.ancestor_artifacts with an explicit call that always seeds a safe
default: keep the WorkflowJobTemplate type check
(isinstance(spawn_node.unified_job_template, WorkflowJobTemplate)) and
unconditionally call
job.seed_root_ancestor_artifacts(spawn_node.ancestor_artifacts or {}) so empty
dicts no longer skip seeding but None still results in an empty dict being
passed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 58d9b2c7-b0b1-4bf2-9333-1360538c8448

📥 Commits

Reviewing files that changed from the base of the PR and between e80ce43 and 1d5a385.

📒 Files selected for processing (4)
  • awx/main/models/workflow.py
  • awx/main/scheduler/task_manager.py
  • awx/main/tests/data/projects/debug/set_stats.yml
  • awx/main/tests/live/tests/test_nested_workflow_artifacts.py

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
awx/main/tests/live/tests/test_nested_workflow_artifacts.py (1)

100-106: Assert terminal success states after wait_for_job for clearer failures.

wait_for_job guarantees completion, not correctness. Adding explicit success assertions for spawned workflow/job nodes will make failures deterministic and easier to triage in CI.

Suggested patch
@@
     outer_wfj = outer_wft.create_unified_job()
     outer_wfj.signal_start()
     wait_for_job(outer_wfj, running_timeout=120)
+    outer_wfj.refresh_from_db()
+    assert outer_wfj.status == 'successful', f'Outer workflow failed with status={outer_wfj.status}'
@@
     inner_wfj = inner_wf_node.job
     assert inner_wfj is not None, 'Inner workflow job was never created'
+    inner_wfj.refresh_from_db()
+    assert inner_wfj.status == 'successful', f'Inner workflow failed with status={inner_wfj.status}'
@@
     reader_node = inner_wfj.workflow_job_nodes.get(identifier='reader')
     assert reader_node.job is not None, 'Reader job was never created'
+    reader_node.job.refresh_from_db()
+    assert reader_node.job.status == 'successful', f'Reader job failed with status={reader_node.job.status}'
@@
     wfj = wft.create_unified_job()
     wfj.signal_start()
     wait_for_job(wfj, running_timeout=120)
+    wfj.refresh_from_db()
+    assert wfj.status == 'successful', f'Workflow failed with status={wfj.status}'

As per coding guidelines, "Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity."

Also applies to: 196-200

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@awx/main/tests/live/tests/test_nested_workflow_artifacts.py` around lines 100
- 106, The test calls wait_for_job(outer_wfj, ...) which only ensures completion
but not success; after waiting assert that outer_wfj.status (or
outer_wfj.node_instance.status if applicable) equals the successful terminal
state and likewise assert inner_wfj.status == 'successful' (or use the project's
canonical success sentinel) after obtaining inner_wf_node and inner_wfj to make
failures deterministic; apply the same pattern for the similar block around
lines 196-200 by adding explicit success assertions for the spawned workflow/job
nodes (use the exact attributes used elsewhere in tests, e.g., outer_wfj.status
and inner_wfj.status or their .status_text/.summary fields).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@awx/main/tests/live/tests/test_nested_workflow_artifacts.py`:
- Around line 100-106: The test calls wait_for_job(outer_wfj, ...) which only
ensures completion but not success; after waiting assert that outer_wfj.status
(or outer_wfj.node_instance.status if applicable) equals the successful terminal
state and likewise assert inner_wfj.status == 'successful' (or use the project's
canonical success sentinel) after obtaining inner_wf_node and inner_wfj to make
failures deterministic; apply the same pattern for the similar block around
lines 196-200 by adding explicit success assertions for the spawned workflow/job
nodes (use the exact attributes used elsewhere in tests, e.g., outer_wfj.status
and inner_wfj.status or their .status_text/.summary fields).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5685869f-88fa-4924-b29e-f87749f7234e

📥 Commits

Reviewing files that changed from the base of the PR and between 1d5a385 and 3e8f242.

📒 Files selected for processing (2)
  • awx/main/models/workflow.py
  • awx/main/tests/live/tests/test_nested_workflow_artifacts.py
✅ Files skipped from review due to trivial changes (1)
  • awx/main/models/workflow.py

@AlanCoding AlanCoding requested review from pb82 April 1, 2026 14:17
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 1, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
78.6% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants