From efb873eb422b559553bfcf6d14872cd72ab4f835 Mon Sep 17 00:00:00 2001 From: nityam <185968020+Nixxx19@users.noreply.github.com> Date: Sat, 5 Sep 2026 20:41:28 +0530 Subject: [PATCH 1/2] recompute eager after prepending global inputs Signed-off-by: nityam <185968020+Nixxx19@users.noreply.github.com> --- .../turborepo-engine/src/task_definition.rs | 49 ++++++++++++++++ crates/turborepo-turbo-json/src/lib.rs | 11 ++-- crates/turborepo-types/src/lib.rs | 9 +++ crates/turborepo/tests/global_inputs_test.rs | 57 +++++++++++++++++++ 4 files changed, 121 insertions(+), 5 deletions(-) diff --git a/crates/turborepo-engine/src/task_definition.rs b/crates/turborepo-engine/src/task_definition.rs index ce790117f6b3d..23a9b34969599 100644 --- a/crates/turborepo-engine/src/task_definition.rs +++ b/crates/turborepo-engine/src/task_definition.rs @@ -177,6 +177,7 @@ pub fn prepend_global_inputs( .collect(); global_globs.append(&mut inputs.globs); inputs.globs = global_globs; + inputs.refresh_eager(); } #[cfg(test)] @@ -213,6 +214,54 @@ mod tests { ); } + #[test] + fn test_prepend_global_inputs_marks_jit_only_task_eager() { + let path_to_root = RelativeUnixPathBuf::new("../..").expect("valid path"); + let mut inputs = TaskInputs { + globs: vec![], + default: false, + jit_globs: vec!["src/**".to_string()], + jit_default: false, + eager: false, + ..Default::default() + }; + + prepend_global_inputs( + &mut inputs, + true, + &["config.txt".to_string()], + &path_to_root, + ); + + assert_eq!(inputs.globs, vec!["../../config.txt"]); + assert!( + inputs.eager, + "task is no longer jit only once global inputs are prepended, so the eager pass has \ + to run or those globs never get hashed" + ); + } + + #[test] + fn test_prepend_global_inputs_leaves_jit_only_task_alone_without_global_inputs() { + let path_to_root = RelativeUnixPathBuf::new("../..").expect("valid path"); + let mut inputs = TaskInputs { + globs: vec![], + default: false, + jit_globs: vec!["src/**".to_string()], + jit_default: false, + eager: false, + ..Default::default() + }; + + prepend_global_inputs(&mut inputs, true, &[], &path_to_root); + + assert!(inputs.globs.is_empty()); + assert!( + !inputs.eager, + "a task that is still jit only should not start hashing eagerly" + ); + } + #[test] fn test_prepend_global_inputs_sets_default_when_no_explicit_inputs() { let path_to_root = RelativeUnixPathBuf::new("../..").expect("valid path"); diff --git a/crates/turborepo-turbo-json/src/lib.rs b/crates/turborepo-turbo-json/src/lib.rs index 9bd5ca13b3a30..64f03f49fd8f0 100644 --- a/crates/turborepo-turbo-json/src/lib.rs +++ b/crates/turborepo-turbo-json/src/lib.rs @@ -388,16 +388,17 @@ impl TaskInputsFromProcessed for turborepo_types::TaskInputs { .map(|glob| glob.resolve(turbo_root_path)) .collect(), }); - let eager = default || !globs.is_empty() || (!jit_default && jit_globs.is_empty()); - - Ok(turborepo_types::TaskInputs { + let mut inputs = turborepo_types::TaskInputs { globs, default, jit_globs, jit_default, dependency_outputs, - eager, - }) + eager: false, + }; + inputs.refresh_eager(); + + Ok(inputs) } } diff --git a/crates/turborepo-types/src/lib.rs b/crates/turborepo-types/src/lib.rs index d7c80a89bc91c..45bb3409d651a 100644 --- a/crates/turborepo-types/src/lib.rs +++ b/crates/turborepo-types/src/lib.rs @@ -560,6 +560,15 @@ impl Default for TaskInputs { } impl TaskInputs { + /// Recomputes `eager` from the current globs. Call this after changing + /// `globs` or `default`, otherwise a task that started out JIT-only keeps + /// `eager: false` and its eager globs never get hashed. + pub fn refresh_eager(&mut self) { + self.eager = self.default + || !self.globs.is_empty() + || (!self.jit_default && self.jit_globs.is_empty()); + } + /// Creates a new TaskInputs with the given globs and default set to false pub fn new(globs: Vec) -> Self { Self { diff --git a/crates/turborepo/tests/global_inputs_test.rs b/crates/turborepo/tests/global_inputs_test.rs index af4aa0be36df2..c0e7381d94b7c 100644 --- a/crates/turborepo/tests/global_inputs_test.rs +++ b/crates/turborepo/tests/global_inputs_test.rs @@ -29,6 +29,20 @@ const TURBO_JSON_GLOBAL_INPUTS: &str = r#"{ } "#; +const TURBO_JSON_GLOBAL_INPUTS_JIT: &str = r#"{ + "futureFlags": { "globalConfiguration": true }, + "global": { + "inputs": ["config.txt"] + }, + "tasks": { + "build": { + "inputs": [{ "mode": "jit", "globs": ["src/**"] }], + "outputs": [] + } + } +} +"#; + const TURBO_JSON_GLOBAL_INPUTS_TOPO: &str = r#"{ "futureFlags": { "globalConfiguration": true }, "global": { @@ -282,3 +296,46 @@ fn test_global_inputs_exclusion_not_defeated_by_phantom_dependency_tasks() { "expected app-b cache hit (phantom dep should not defeat negation), got: {combined}" ); } + +/// A task whose only `inputs` are JIT still has `global.inputs` prepended to +/// its eager globs, so a change to a global input file must invalidate it. +#[test] +fn test_global_inputs_invalidate_jit_only_task() { + let tempdir = tempfile::tempdir().unwrap(); + setup_fixture(tempdir.path(), TURBO_JSON_GLOBAL_INPUTS_JIT); + + let output = run_turbo( + tempdir.path(), + &["build", "-F", "app-a", "--output-logs=hash-only"], + ); + assert!(output.status.success()); + let combined = combined_output(&output); + assert!( + combined.contains("cache miss"), + "expected cache miss on first run, got: {combined}" + ); + + let output = run_turbo( + tempdir.path(), + &["build", "-F", "app-a", "--output-logs=hash-only"], + ); + assert!(output.status.success()); + let combined = combined_output(&output); + assert!( + combined.contains("cache hit"), + "expected cache hit on second run, got: {combined}" + ); + + fs::write(tempdir.path().join("config.txt"), "changed value").unwrap(); + + let output = run_turbo( + tempdir.path(), + &["build", "-F", "app-a", "--output-logs=hash-only"], + ); + assert!(output.status.success()); + let combined = combined_output(&output); + assert!( + combined.contains("cache miss"), + "expected cache miss after changing a global input, got: {combined}" + ); +} From b6a028e19fc0f239d5a7d089a5a20e301dc1027b Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Sun, 6 Sep 2026 10:52:51 -0600 Subject: [PATCH 2/2] fix: Simplify eager hashing for global inputs --- crates/turborepo-engine/src/task_definition.rs | 2 +- crates/turborepo-turbo-json/src/lib.rs | 11 +++++------ crates/turborepo-types/src/lib.rs | 9 --------- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/crates/turborepo-engine/src/task_definition.rs b/crates/turborepo-engine/src/task_definition.rs index 23a9b34969599..9258772a0c003 100644 --- a/crates/turborepo-engine/src/task_definition.rs +++ b/crates/turborepo-engine/src/task_definition.rs @@ -177,7 +177,7 @@ pub fn prepend_global_inputs( .collect(); global_globs.append(&mut inputs.globs); inputs.globs = global_globs; - inputs.refresh_eager(); + inputs.eager = true; } #[cfg(test)] diff --git a/crates/turborepo-turbo-json/src/lib.rs b/crates/turborepo-turbo-json/src/lib.rs index 64f03f49fd8f0..9bd5ca13b3a30 100644 --- a/crates/turborepo-turbo-json/src/lib.rs +++ b/crates/turborepo-turbo-json/src/lib.rs @@ -388,17 +388,16 @@ impl TaskInputsFromProcessed for turborepo_types::TaskInputs { .map(|glob| glob.resolve(turbo_root_path)) .collect(), }); - let mut inputs = turborepo_types::TaskInputs { + let eager = default || !globs.is_empty() || (!jit_default && jit_globs.is_empty()); + + Ok(turborepo_types::TaskInputs { globs, default, jit_globs, jit_default, dependency_outputs, - eager: false, - }; - inputs.refresh_eager(); - - Ok(inputs) + eager, + }) } } diff --git a/crates/turborepo-types/src/lib.rs b/crates/turborepo-types/src/lib.rs index 45bb3409d651a..d7c80a89bc91c 100644 --- a/crates/turborepo-types/src/lib.rs +++ b/crates/turborepo-types/src/lib.rs @@ -560,15 +560,6 @@ impl Default for TaskInputs { } impl TaskInputs { - /// Recomputes `eager` from the current globs. Call this after changing - /// `globs` or `default`, otherwise a task that started out JIT-only keeps - /// `eager: false` and its eager globs never get hashed. - pub fn refresh_eager(&mut self) { - self.eager = self.default - || !self.globs.is_empty() - || (!self.jit_default && self.jit_globs.is_empty()); - } - /// Creates a new TaskInputs with the given globs and default set to false pub fn new(globs: Vec) -> Self { Self {