chore(core): unify Task into a single Rust struct#35540
Merged
AgentEnder merged 8 commits intomasterfrom May 4, 2026
Merged
Conversation
The TS Task interface and the Rust Task struct were two separate definitions that drifted: the Rust copy was missing overrides, hash, hashDetails, cache, and parallelism. Make the Rust struct in packages/nx/src/native/tasks/types.rs the canonical superset and have TS re-export it through ../native, so Task / TaskGraph / TaskTarget / TaskResult / TaskHashDetails are all defined in one place. This unblocks moving the task orchestrator and scheduler to Rust. The historical blocker for unifying was Task.overrides (a freeform POJO of CLI args). Enable napi's serde-json feature so the field can be modeled as serde_json::Value, surfaced in TS as Record<string, unknown>. TaskResult.status gets the literal-union TS type via #[napi(ts_type)], letting us drop the duplicate TaskResult in tasks-runner/life-cycle.ts. Behavior is unchanged on every existing code path. The legacy overrides: Object parameter annotations in create-task-graph.ts and hash-plan-inspector.ts are upgraded to Record<string, unknown>; direct overrides reads (combineOptionsForExecutor call sites, __overrides_unparsed__) get tight casts that preserve the prior runtime semantics including the throw-on-missing behavior.
The TUI / dep-outputs Rust tests were spelling out every Task field with placeholder Nones. Add a Task::new(id, project, target) constructor that fills in the canonical required fields and leaves the rest at Default, so tests can use struct-update syntax to override only what they care about. Net -423 lines from test fixtures, and adding new fields to Task no longer requires touching every test site.
The struct-update form was still requiring Task { ... } at every test
site. Add with_project_root / with_outputs / with_continuous /
with_configuration / with_hash / with_cache / with_parallelism /
with_overrides on Task, all consuming self by value so they chain.
Test fixtures now read as plain method chains:
Task::new("task1", "app1", "test")
.with_project_root("")
.with_continuous(false)
instead of:
Task {
project_root: Some("".to_string()),
continuous: Some(false),
..Task::new("task1", "app1", "test")
}
Task::new now takes (project, target) and derives the id as
"{project}:{target}", matching the formula the TS task-graph builder
already uses (packages/nx/src/tasks-runner/create-task-graph.ts).
with_configuration recomputes the id with the configuration suffix.
A with_id escape hatch exists for the small handful of test fixtures
that depend on a specific non-canonical id (length-sensitive column
visibility tests, pinned-task width arithmetic).
TS construction is unaffected. createTaskGraph still builds the id
explicitly when assembling the task literal, just like today.
Test code that previously queried tasks by their non-canonical ids
("app1", "task1", etc.) now uses the canonical "{project}:{target}"
form. Snapshot expectations were regenerated for the new display
strings.
✅ Deploy Preview for nx-dev ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for nx-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Contributor
|
View your CI Pipeline Execution ↗ for commit 84fe001
☁️ Nx Cloud last updated this comment at |
Remove with_configuration / with_hash / with_cache / with_parallelism /
with_overrides — none had callers. Keeping with_project_root /
with_outputs / with_continuous (the three actually used in test
fixtures).
Also extract getUnparsedOverrideArgs(task) in tasks-runner/utils.ts
and use it at the three call sites that were repeating
(task.overrides as { __overrides_unparsed__: string[] }).
__overrides_unparsed__.
Trim a now-redundant inline example from the Task::new doc.
Restores the ability for callers of initTasksRunner.invoke (and any other JS-side Task constructor) to omit `parallelism`. napi-rs was rejecting hand-built Task objects with `Missing field parallelism on TaskGraph.tasks` because the unified Rust struct declared it as a required `bool`. Mirrors the optionality of `cache` and `continuous`. Adds `Task::is_parallelizable()` so future Rust readers default `None` to `true`, matching `create-task-graph.ts` (`parallelism ?? true`).
…f-Healing CI Rerun]
Contributor
There was a problem hiding this comment.
Nx Cloud has identified a flaky task in your failed CI:
🔂 Since the failure was identified as flaky, we triggered a CI rerun by adding an empty commit to this branch.
🎓 Learn more about Self-Healing CI on nx.dev
AgentEnder
reviewed
May 2, 2026
Comment on lines
-8
to
+10
| "> ✔ task1 - ..." | ||
| " ✔ task2 - ..." | ||
| " ✔ task3 - ..." | ||
| "> ✔ app1:build - ..." | ||
| " ✔ app1:test - ..." | ||
| " ✔ app2:lint - ..." |
Member
There was a problem hiding this comment.
Suspicious. Why did these change?
AgentEnder
approved these changes
May 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Current Behavior
Taskis defined twice and the two definitions have drifted:packages/nx/src/config/task-graph.ts— 13 fields includingoverrides: any,hash,hashDetails,cache,parallelism.packages/nx/src/native/tasks/types.rs— 7 fields, missing all the dynamic / options-shaped ones.Plus a swarm of variant types (
TaskResultdefined in both Rust and TS,TaskTarget, etc.). Anything that wants to consume a Task in Rust either gets the lossy 7-field copy or has to translate at the boundary. The historical blocker for unifying wasTask.overrides— a freeform POJO of CLI args that had no obvious Rust representation.Expected Behavior
One canonical
Taskstruct, defined in Rust, exposed to TS via NAPI:packages/nx/src/native/tasks/types.rsis now the superset (all 13 fields).overridesis modeled asserde_json::Value, surfaced in TS asRecord<string, unknown>. Theserde-jsonnapi feature is enabled to make this round-trip work.TaskResult.statusgets the literal-union TS type via#[napi(ts_type)]so we can collapse the duplicateTaskResultintasks-runner/life-cycle.tsinto a re-export from the native bindings.packages/nx/src/config/task-graph.tsnow re-exportsTask/TaskGraph/TaskTarget/TaskHashDetailsfrom../native. Every existing import keeps working.Task::new(project, target)(id auto-derived as{project}:{target}, matching the formulacreate-task-graph.tsalready uses) plus chainedwith_*builders for the optional fields.with_configurationrecomputes the id with the configuration suffix.Behavior is unchanged on every existing TS code path. The legacy
overrides: Objectparameter annotations increate-task-graph.tsandhash-plan-inspector.tsare tightened toRecord<string, unknown>. Directoverridesreads atcombineOptionsForExecutorcall sites and__overrides_unparsed__accesses get tight casts that preserve prior runtime semantics, including the throw-on-missing behavior.This unblocks porting the task scheduler (and eventually the orchestrator) to Rust — without one shared struct, every port has to redo the boundary translation.
Verification
cargo test --lib— 352 passed, 0 failed.pnpm nx build nx— clean.pnpm nx run-many -t build— all packages build (the twoexamples-angular-rspack-mf-*failures predate this branch).pnpm nx run nx:lint— clean (only a pre-existing skipped-test warning).pnpm nx run nx:test --testPathPatterns='create-task-graph|tasks-runner|hasher'— all suites pass, includingshould interpolate overrides.Out of scope
HashedTask(history DB row) andTaskRun(history record) — they are storage shapes, not Tasks.Related Issue(s)
Foundation for the eventual Rust port of the task scheduler and orchestrator.