fix(task): invalidate dependent task sources when dependency runs#8975
fix(task): invalidate dependent task sources when dependency runs#8975
Conversation
When a task with `sources` depends on another task, and the dependency actually runs (its own sources changed), the dependent task now also runs even if its own sources haven't changed. This fixes monorepo workflows where downstream tasks should re-run when upstream tasks are invalidated. Closes #8960 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR implements transitive source invalidation for mise task dependencies: when a task with The logic is sound — Confidence Score: 5/5Safe to merge — logic is correct, well-tested, and the race-free ordering of mark_ran → remove → emit_leaves is preserved. All findings are P2 or lower. The core invariant (mark_ran before remove/emit_leaves) is correctly maintained, dep_ran is read atomically with completed under the same mutex acquisition, and transitive propagation works naturally through the chain. The e2e test covers the three key scenarios. No files require special attention.
|
| Filename | Overview |
|---|---|
| src/task/deps.rs | Adds ran set and dep_edges map with mark_ran / any_dep_ran methods; edge-building correctly restricted to pre-deps only. |
| src/cli/run.rs | Reads dep_ran and completed atomically under the mutex before spawning the task body, then conditionally calls mark_ran after execution — ordering relative to deps.remove is correct. |
| src/task/task_executor.rs | Signature change to return Result<bool> and accept dep_ran: bool; freshness bypass condition !dep_ran is correctly placed before the async sources_are_fresh call. |
| e2e/tasks/test_task_dep_invalidates_sources | New e2e test covering the core scenario and two edge cases (sourceless dep, sourceless dependent); uses standard assertion helpers. |
| docs/tasks/task-configuration.md | Adds a clear "Dependency invalidation" subsection with a realistic monorepo example explaining the new behavior. |
Sequence Diagram
sequenceDiagram
participant Scheduler
participant DepTask as Dep Task (has sources)
participant Deps
participant DownstreamTask as Downstream Task (has sources)
Scheduler->>Deps: emit_leaves() → schedules DepTask
Scheduler->>DepTask: spawn_sched_job
DepTask->>Deps: mark_executed(dep)
DepTask->>DepTask: run_task_sched(dep_ran=false)
Note over DepTask: sources changed → runs → returns Ok(true)
DepTask->>Deps: mark_ran(dep) [sources non-empty]
DepTask->>Deps: remove(dep) → emit_leaves() → schedules DownstreamTask
Scheduler->>DownstreamTask: spawn_sched_job
DownstreamTask->>Deps: mark_executed(downstream)
DownstreamTask->>Deps: any_dep_ran(downstream) → true
DownstreamTask->>DownstreamTask: run_task_sched(dep_ran=true)
Note over DownstreamTask: dep_ran=true → skips sources_are_fresh → runs
DownstreamTask->>Deps: mark_ran(downstream) [sources non-empty]
DownstreamTask->>Deps: remove(downstream)
Reviews (5): Last reviewed commit: "docs(task): document dependency source i..." | Re-trigger Greptile
Covers: dep with no sources (always runs) invalidates dependent, and dep with sources skipped doesn't affect sourceless dependent. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tasks without sources always run but should not invalidate their dependents' source freshness checks. Only tasks that have sources defined and actually ran (because those sources changed) should trigger downstream invalidation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request updates the task execution logic to ensure that dependent tasks are correctly invalidated when their upstream dependencies execute, even if the dependent task's own sources have not changed. This is achieved by tracking which tasks actually ran and propagating this state through the dependency graph. I have reviewed the suggested changes and agree with the feedback regarding performance optimization by hoisting the task key calculation and the functional requirement to include post-dependencies in the dependency tracking map.
| dep_edges | ||
| .entry(task_key(&a)) | ||
| .or_default() | ||
| .insert(task_key(&b)); |
There was a problem hiding this comment.
There are two points for improvement in this section:
-
Performance Inefficiency:
task_key(&a)is called repeatedly inside thefor b in preloop. Sinceais constant for the duration of the loop, its key should be computed once before the loop starts to avoid redundant string cloning and sorting operations. -
Functional Omission for Post-Dependencies: The
dep_edgesmap is currently only updated forpredependencies. However,postdependencies (tasks that run after the current task) also logically depend on the parent task. To ensure that a post-dependency task's sources are correctly invalidated when its parent task runs, you should also updatedep_edgesinside thefor b in postloop (mappingbtoa). Without this, the new invalidation logic will not work for tasks defined independs_post.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.7 x -- echo |
25.2 ± 1.2 | 23.9 | 38.0 | 1.00 |
mise x -- echo |
25.4 ± 0.5 | 24.2 | 26.8 | 1.01 ± 0.05 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.7 env |
24.3 ± 0.7 | 23.1 | 30.9 | 1.00 |
mise env |
24.7 ± 0.9 | 23.5 | 34.3 | 1.02 ± 0.05 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.7 hook-env |
25.8 ± 0.9 | 24.4 | 40.4 | 1.01 ± 0.04 |
mise hook-env |
25.5 ± 0.5 | 24.7 | 27.3 | 1.00 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.7 ls |
22.2 ± 0.4 | 21.3 | 23.6 | 1.00 |
mise ls |
23.0 ± 1.1 | 21.8 | 35.6 | 1.04 ± 0.05 |
xtasks/test/perf
| Command | mise-2026.4.7 | mise | Variance |
|---|---|---|---|
| install (cached) | 158ms | 157ms | +0% |
| ls (cached) | 82ms | 82ms | +0% |
| bin-paths (cached) | 89ms | 87ms | +2% |
| task-ls (cached) | 810ms | 802ms | +0% |
### 🚀 Features - **(config)** add lockfile_platforms setting to restrict lockfile platforms by @cameronbrill in [#8966](#8966) - **(sandbox)** support wildcard patterns in allow_env by @jdx in [#8974](#8974) - bump usage-lib v2 → v3 to render examples in task --help by @baby-joel in [#8890](#8890) ### 🐛 Bug Fixes - **(activate)** handle empty __MISE_FLAGS array with set -u on bash 3.2 by @jdx in [#8988](#8988) - **(env)** add trace logging for module hook PATH diagnostics by @jdx in [#8981](#8981) - **(go)** Query module proxy directly for version resolution by @c22 in [#8968](#8968) - **(install)** render tera templates in tool postinstall hooks by @jdx in [#8978](#8978) - **(install)** add missing env vars to tool postinstall hooks by @jdx in [#8977](#8977) - **(task)** prevent hang when skipped task has dependents by @jdx in [#8937](#8937) - **(task)** invalidate dependent task sources when dependency runs by @jdx in [#8975](#8975) - **(task)** prevent deadlock when MISE_JOBS=1 with sub-task references by @jdx in [#8976](#8976) - **(task)** fetch remote task files before parsing usage specs by @jdx in [#8979](#8979) - **(task)** prevent panic when running parallel sub-tasks with replacing output by @jdx in [#8986](#8986) - **(upgrade)** update lockfile and config when upgrading to specific version by @jdx in [#8983](#8983) ### 📚 Documentation - **(node)** remove "recommended for teams" from pin example by @jdx in [b334363](b334363) ### 📦️ Dependency Updates - update ghcr.io/jdx/mise:alpine docker digest to 17a29f2 by @renovate[bot] in [#8995](#8995) - update docker/dockerfile:1 docker digest to 2780b5c by @renovate[bot] in [#8994](#8994) ### New Contributors - @baby-joel made their first contribution in [#8890](#8890) - @cameronbrill made their first contribution in [#8966](#8966) - @c22 made their first contribution in [#8968](#8968)
Summary
sourcesdepends on another task that also hassources, and the dependency actually runs (its sources changed), the dependent task now also runs even if its own sources haven't changedsourcesdefined propagate invalidation — sourceless deps (which always run) do not trigger downstream re-runs, sosourceson dependents remain usefulFixes #8960
Test plan
test_task_dep_invalidates_sourcescovers:tscsetup as described in the discussion🤖 Generated with Claude Code
Note
Medium Risk
Changes task scheduling/skip logic for
sources-based caching, which can affect when tasks execute or are skipped in complex dependency graphs. Risk is mitigated by new end-to-end coverage, but may surface edge cases with nested/parallel dependencies.Overview
Fixes
sourcescaching behavior so a task withsourceswill re-run when any of its direct dependencies withsourcesactually ran (rather than being skipped as up-to-date).Implements this by tracking which tasks truly executed vs were skipped in
Depsand passing adep_ranflag intoTaskExecutor::run_task_schedto bypasssources_are_freshchecks when needed; sourceless dependencies (which always run) do not trigger invalidation. Adds an e2e regression test and updates docs to describe the new dependency invalidation semantics.Reviewed by Cursor Bugbot for commit 1491ab8. Bugbot is set up for automated code reviews on this repo. Configure here.