Skip to content

feat(task): explain task cache keys - #11595

Merged
jdx merged 1 commit into
mainfrom
agent/explain-task-cache-keys
Aug 1, 2026
Merged

jdx merged 1 commit into
mainfrom
agent/explain-task-cache-keys

Conversation

@jdx

@jdx jdx commented Aug 1, 2026 •

Copy link
Copy Markdown
Owner

Stack created with GitHub Stacks CLI • Give Feedback 💬

Summary

  • add mise run --task-cache-explain <task>
  • report cache-key input categories, counts, environment names/presence, variable names, and platform without emitting secret-derived hashes
  • support explicit cache explanations during dry-run and raw execution while still bypassing cache restore/write behavior
  • make pnpm lockfile handling fall back to ordinary affected-path mapping when the graph has no Node projects
  • compute explanation details only when requested
  • update generated CLI docs, the cache guide, tests, and the parity tracker

Why

Task output cache keys were opaque, which made it difficult to understand what contributes to them. The explanation is deliberately structural: potentially secret-bearing values and hashes are unavailable to the renderer. Review also identified that dry-run skipped explanations and that an empty pnpm provider result could swallow a changed path; both are corrected here because the affected-task prerequisite PRs have merged.

User impact

Cache-enabled tasks can inspect key inputs without executing, restoring, or storing task output. Ordinary runs do not pay diagnostic construction costs. A pnpm lockfile in a non-Node workspace now affects projects through the normal workspace-global path rules.

Validation

  • cargo test cache_explanation_omits_environment_and_variable_values -- --nocapture
  • cargo test cli::tests::test_subcommands_are_sorted -- --nocapture
  • mise run test:e2e e2e/tasks/test_task_affected e2e/tasks/test_task_artifact_cache
  • mise run render
  • mise run lint
  • cargo clippy --workspace --all-features --all-targets -- -D warnings

AI-assisted — Tool: Codex; model: unavailable/unavailable; version: unavailable.


Note

Medium Risk
Touches task cache key construction and affected-project lockfile routing; behavior is guarded by an opt-in flag and e2e tests, but cache and monorepo selection paths are user-visible when enabled.

Overview
Adds mise run --task-cache-explain so cache-enabled tasks can print a structural breakdown of what feeds the output cache key—categories, counts, env/var names and set/unset state, and platform—without emitting the aggregate key or secret-bearing hashes.

The executor builds that explanation only when the flag is set, prints it before cache logic, and still skips restore/write under --dry-run or raw/interactive runs while allowing explain (including running cache command inputs when needed for the key). CLI wiring, generated docs, task-configuration guide, e2e coverage, and the parity tracker are updated accordingly.

--affected lockfile handling now treats an empty provider attribution as “not handled” and falls back to ordinary changed-path / workspace-global mapping, so an unrelated pnpm-lock.yaml in a non-Node workspace is no longer swallowed.

Reviewed by Cursor Bugbot for commit 864c270. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features

    • Added --task-cache-explain to mise run and mise tasks run.
    • Displays task cache keys and a deterministic breakdown of their inputs, including sources, dependencies, tools, platform, and command outputs.
    • Redacts environment and variable values while preserving useful cache details.
    • Supports explanations during dry runs without executing tasks.
  • Bug Fixes

    • Improved affected-project detection for pnpm lockfile changes in Cargo workspaces.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds --task-cache-explain to task commands. It generates redacted cache-key explanations, supports dry runs, documents the option, and adds coverage for cache output and pnpm lockfile affected-task handling.

Changes

Task cache explanations and affected-task handling

Layer / File(s) Summary
Cache-key explanation model
src/task/task_cache.rs, e2e/tasks/test_task_artifact_cache
Cache construction records counts, names, platform data, and environment presence without exposing values. Tests verify formatting and redaction.
Task executor cache flow
src/task/task_executor.rs, e2e/tasks/test_task_artifact_cache
The executor propagates explanation mode, prepares inputs for explanation requests, bypasses cache use for dry runs and raw execution, and prints explanations.
CLI and affected-task integration
src/cli/run.rs, src/cli/bootstrap.rs, src/cli/mod.rs, mise.usage.kdl, man/man1/mise.1, docs/cli/*, docs/tasks/*, TASK_CACHE_PARITY.md, e2e/tasks/test_task_affected
The run commands expose the flag and affected-task processing handles pnpm lockfile changes. Documentation and end-to-end tests describe and validate the behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Run
  participant TaskExecutor
  participant TaskArtifactCache
  Run->>TaskExecutor: enable task_cache_explain
  TaskExecutor->>TaskArtifactCache: build cache-key explanation
  TaskArtifactCache-->>TaskExecutor: return redacted explanation
  TaskExecutor-->>Run: print explanation
Loading

Possibly related PRs

  • jdx/mise#11328: Extends task artifact-cache behavior that this change uses for cache-key explanation plumbing.
  • jdx/mise#11340: Both changes modify task cache-key generation and dependency-related explanation data.
  • jdx/mise#11589: Relates to affected-task selection for workspace lockfile changes.

Suggested reviewers: risu729

Poem

A rabbit reads each cache-key thread,
With secret values safely shed.
The task runs dry, yet clues appear,
Lockfile changes hop far and near.
“Explain it cleanly!” the rabbit said.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding task cache key explanations.

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.

Base automatically changed from agent/affected-json to main August 1, 2026 20:42
@greptile-apps

greptile-apps Bot commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds a structural explanation for task-cache key inputs while preventing cache restore/write during diagnostic dry-run and raw execution. It also ensures unmatched pnpm lockfile changes fall back to ordinary affected-path handling.

  • Adds and documents --task-cache-explain.
  • Reports only input categories, counts, names, presence state, and platform.
  • Adds unit and end-to-end coverage for secret-safe explanations and cache bypass behavior.
  • Corrects affected-project handling for pnpm lockfiles outside Node workspaces.

Confidence Score: 5/5

The PR appears safe to merge.

The two previously reported secret-oracle paths are closed because the diagnostic output now contains only structural metadata and does not emit either per-value digests or the aggregate cache key; no blocking failure remains.

Important Files Changed

Filename Overview
src/task/task_cache.rs Builds and renders cache explanations exclusively from structural metadata; neither secret-derived digests nor the aggregate key are emitted.
src/task/task_executor.rs Wires explanation output into execution while bypassing artifact-cache reads and writes for dry-run and raw modes.
src/cli/run.rs Propagates the new option and falls back to regular changed-path mapping when a recognized lockfile produces no provider projects.
e2e/tasks/test_task_artifact_cache Verifies explanation output, omission of values and aggregate keys, and dry-run non-execution.
e2e/tasks/test_task_affected Covers pnpm lockfile fallback in a workspace without Node projects.

Reviews (3): Last reviewed commit: "feat(task): explain task cache keys" | Re-trigger Greptile

Comment thread src/task/task_cache.rs Resolved
@github-actions

github-actions Bot commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

Instruction counts

benchmark trend instructions Δ wall (min) Δ
env ▃▁▁▅▅▄▄▂▃▄▄▂█▅ 84,916,587 → 84,838,558 -0.09% 20.55 → 18.27ms -11.09%
hook-env ▄▃▁▆▇▅▅▂▄▃▄▆█▄ 87,229,419 → 87,127,047 -0.12% 21.01 → 18.49ms -11.98%
ls ▄▁▂▆▅▆▆▄▅▄▅▅██ 76,805,312 → 76,796,755 -0.01% 16.35 → 19.33ms +18.23%
registry ▁▅▄▂▂██▇████▄▂ 50,331,177 → 50,267,935 -0.13% 11.61 → 13.87ms +19.47%
startup ▄▅▄▄▄▅▄▄▄▄▄▄█▁ 16,156,389 → 16,060,463 -0.59% 8.40 → 8.95ms +6.50%

No instruction-count regression above 1%.

Only instruction counts gate. Wall clock is shown for context — on identical hardware it moves 4-20% run to run.

Measured by tak — instruction-counted CLI benchmarks, stored in this repository's git notes.

864c2703ecfd vs 7f614f72578c · measured on the runner, not pushed to the history.

@jdx
jdx force-pushed the agent/explain-task-cache-keys branch from 4e39e95 to f23ca56 Compare August 1, 2026 20:55

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f23ca56. Configure here.

Comment thread src/task/task_executor.rs Resolved
Comment thread src/cli/run.rs Resolved
Comment thread src/task/task_cache.rs Resolved

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (4)
src/config/mod.rs (1)

526-535: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Replace check-then-set caching with an atomic get-or-init.

workspace_project_graph_for_task_loading locks, checks, unlocks, then computes and locks again to store the result. Two callers can both observe None at the same time, both run discover_workspace_project_graph() (a full filesystem scan), and both write. The result is redundant work, not corrupted data, since discovery is a pure read and any run's output is equally valid to cache. Use once_cell::sync::OnceCell::get_or_try_init (already used elsewhere in this file, for example env: OnceCell<EnvResults>) so concurrent callers block on the first computation instead of duplicating it.

♻️ Proposed refactor
-    workspace_project_graph_cache:
-        Mutex<Option<Arc<crate::task::workspace::WorkspaceProjectGraph>>>,
+    workspace_project_graph_cache: OnceCell<Arc<crate::task::workspace::WorkspaceProjectGraph>>,
-    fn workspace_project_graph_for_task_loading(
-        &self,
-    ) -> Result<Arc<crate::task::workspace::WorkspaceProjectGraph>> {
-        if let Some(graph) = self.workspace_project_graph_cache.lock().unwrap().clone() {
-            return Ok(graph);
-        }
-        let graph = Arc::new(self.discover_workspace_project_graph()?);
-        *self.workspace_project_graph_cache.lock().unwrap() = Some(graph.clone());
-        Ok(graph)
-    }
+    fn workspace_project_graph_for_task_loading(
+        &self,
+    ) -> Result<Arc<crate::task::workspace::WorkspaceProjectGraph>> {
+        self.workspace_project_graph_cache
+            .get_or_try_init(|| self.discover_workspace_project_graph().map(Arc::new))
+            .cloned()
+    }

Update every workspace_project_graph_cache: Mutex::new(None) test-fixture initializer to OnceCell::new().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/config/mod.rs` around lines 526 - 535, Replace the Mutex-based
check-then-set logic in workspace_project_graph_for_task_loading with
OnceCell::get_or_try_init, preserving error propagation and returning the cached
Arc. Change workspace_project_graph_cache’s type and every test-fixture
initializer from Mutex::new(None) to OnceCell::new().
src/cli/mod.rs (1)

774-818: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce duplicated Run field enumeration with #[derive(Default)]. Both sites build a full run::Run { ... } literal by hand because Run has no Default implementation. This PR had to add the same 6 new fields to both places, and any future CLI option on Run will require the same duplicated edit at every construction site.

  • src/cli/mod.rs#L774-L818: build the naked-task Run as run::Run { task, args: ..., affected: false, ..Default::default() }, keeping only the fields that differ from default explicit (e.g. cd: self.cd, task_cache: crate::task::TaskCacheMode::from_env()?).
  • src/cli/bootstrap.rs#L897-L948: build the bootstrap-task Run the same way, keeping only task, dry_run, skip_tools, and task_cache explicit.

This requires adding #[derive(Default)] to the Run struct definition in src/cli/run.rs; most fields already resolve to Default::default() individually at both sites today, so this should compile with minimal further changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli/mod.rs` around lines 774 - 818, Add #[derive(Default)] to the Run
struct in src/cli/run.rs, then replace the full Run literals in
src/cli/mod.rs:774-818 and src/cli/bootstrap.rs:897-948 with Default-based
initialization. In src/cli/mod.rs, retain explicit task, args, affected, and
other non-default values such as cd and task_cache; in src/cli/bootstrap.rs,
retain only task, dry_run, skip_tools, and task_cache as specified.
e2e/tasks/test_task_artifact_cache (1)

27-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Capture the explanation once instead of running the task six times.

Each assert_contains/assert_not_contains call re-invokes PROFILE=debug mise run --task-cache-explain build, so the block runs the task six times just to match substrings of the same output. Store the explanation once and run the assertions against the stored string so failures show the full output.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/tasks/test_task_artifact_cache` around lines 27 - 34, Capture the output
of the PROFILE=debug mise run --task-cache-explain build command once in a
variable, then update all assert_contains and assert_not_contains calls in this
test to inspect that stored explanation instead of rerunning the task. Preserve
all existing substring checks and ensure assertion failures can display the
complete captured output.
src/task/workspace/node.rs (1)

405-444: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid scanning all lockfile entries for each dependency candidate.

resolve_package_keys iterates the whole available set for every candidate path, and this runs during reachable_packages, which is called once per importer in the pnpm diff path. Since available is a BTreeSet containing both packages and snapshots, a prefix range scan can stop after the exact key and its (...) peer variants, avoiding a linear scan over unrelated lockfile entries.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/task/workspace/node.rs` around lines 405 - 444, Optimize
resolve_package_keys by replacing the full available.iter() scan for each
candidate with a BTreeSet range lookup that starts at the candidate and stops
after its exact key and parenthesized peer variants. Preserve matching of the
exact candidate and keys with suffixes beginning with '(' while avoiding
unrelated packages and snapshots.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@e2e/tasks/test_task_artifact_cache`:
- Around line 27-34: Capture the output of the PROFILE=debug mise run
--task-cache-explain build command once in a variable, then update all
assert_contains and assert_not_contains calls in this test to inspect that
stored explanation instead of rerunning the task. Preserve all existing
substring checks and ensure assertion failures can display the complete captured
output.

In `@src/cli/mod.rs`:
- Around line 774-818: Add #[derive(Default)] to the Run struct in
src/cli/run.rs, then replace the full Run literals in src/cli/mod.rs:774-818 and
src/cli/bootstrap.rs:897-948 with Default-based initialization. In
src/cli/mod.rs, retain explicit task, args, affected, and other non-default
values such as cd and task_cache; in src/cli/bootstrap.rs, retain only task,
dry_run, skip_tools, and task_cache as specified.

In `@src/config/mod.rs`:
- Around line 526-535: Replace the Mutex-based check-then-set logic in
workspace_project_graph_for_task_loading with OnceCell::get_or_try_init,
preserving error propagation and returning the cached Arc. Change
workspace_project_graph_cache’s type and every test-fixture initializer from
Mutex::new(None) to OnceCell::new().

In `@src/task/workspace/node.rs`:
- Around line 405-444: Optimize resolve_package_keys by replacing the full
available.iter() scan for each candidate with a BTreeSet range lookup that
starts at the candidate and stops after its exact key and parenthesized peer
variants. Preserve matching of the exact candidate and keys with suffixes
beginning with '(' while avoiding unrelated packages and snapshots.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 66fe3053-0193-49da-b4c4-4830808a9268

📥 Commits

Reviewing files that changed from the base of the PR and between 7f614f7 and f23ca56.

📒 Files selected for processing (18)
  • TASK_CACHE_PARITY.md
  • docs/cli/run.md
  • docs/cli/tasks/run.md
  • docs/tasks/monorepo.md
  • docs/tasks/task-configuration.md
  • e2e/tasks/test_task_affected
  • e2e/tasks/test_task_artifact_cache
  • man/man1/mise.1
  • mise.usage.kdl
  • src/cli/bootstrap.rs
  • src/cli/mod.rs
  • src/cli/run.rs
  • src/config/mod.rs
  • src/git.rs
  • src/task/task_cache.rs
  • src/task/task_executor.rs
  • src/task/workspace.rs
  • src/task/workspace/node.rs

@jdx
jdx force-pushed the agent/explain-task-cache-keys branch from f23ca56 to 9087e2c Compare August 1, 2026 21:06
@jdx
jdx force-pushed the agent/explain-task-cache-keys branch from 9087e2c to 864c270 Compare August 1, 2026 21:07
@jdx
jdx merged commit aec80d6 into main Aug 1, 2026
31 checks passed
@jdx
jdx deleted the agent/explain-task-cache-keys branch August 1, 2026 21:32
This was referenced Aug 1, 2026
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