Repository navigation
feat(task): support negative output patterns - #11367
Conversation
📝 WalkthroughWalkthroughOutput exclusion patterns now control task freshness checks and artifact caching. Matchers filter hashed outputs and cache archives, preserve excluded files during restores, validate configurations, and are documented with unit, end-to-end, schema, and parity updates. ChangesOutput Cache Parity
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant TaskArtifactCache
participant OutputMatcher
participant CacheArchive
participant Workspace
TaskArtifactCache->>OutputMatcher: build matcher from task outputs
TaskArtifactCache->>CacheArchive: archive selected outputs
CacheArchive-->>TaskArtifactCache: filtered artifact
TaskArtifactCache->>CacheArchive: restore artifact
TaskArtifactCache->>OutputMatcher: identify excluded outputs
TaskArtifactCache->>Workspace: preserve excluded local outputs
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
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. Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8a2c790. Configure here.
Greptile SummaryThis PR adds ordered negative output patterns to task freshness and artifact caching.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains within the scope of the available previous review threads. Important Files Changed
Reviews (3): Last reviewed commit: "fix(task): handle excluded output edge c..." | Re-trigger Greptile |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/task/task_source_checker.rs (1)
638-670: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winExcluded-but-missing static output incorrectly aborts the whole hash.
For each
static_pathsentry,path.metadata()is evaluated before exclusion is checked. If a literal declared output (e.g.outputs = ["dist/private/token", "!dist/private/**"]) ends up excluded by a later pattern and also doesn't exist on disk,Err(_) => return Ok(None)fires unconditionally — treating a legitimately-excluded, not-required path as an "incomplete outputs" failure.resolve_output_rootsintask_cache.rshandles the analogous case correctly (it checksis_outputbefore requiring existence); this function should do the same, e.g. by checkingis_outputfirst and only failing on missing-and-selected paths.🐛 Sketch of the fix
for path_str in static_paths { let path = { ... }; match path.metadata() { Ok(m) if m.is_file() => { if is_output(&matcher, &path, false) { entries.push(hash_file(&path)?); - } else { - continue; } } ... - Err(_) => return Ok(None), // missing → outputs incomplete + Err(_) => { + if is_output(&matcher, &path, false) { + return Ok(None); // missing → outputs incomplete + } + } } }🤖 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/task_source_checker.rs` around lines 638 - 670, Update the static_paths handling in the task source-checking function to evaluate is_output before treating metadata failures as incomplete outputs. Skip excluded paths without requiring them to exist, while preserving existing file, directory, and other-entry hashing for selected paths and returning Ok(None) only when a selected path is missing.src/task/task_cache.rs (1)
390-427: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winGlob-pattern "matched" tracking conflates "the glob matched files" with "the matcher selected files," breaking negative-pattern output caching in two places. In both loops, a
matched/incomplete-check flag is only settrueinside theis_output(...)branch, so a glob output pattern whose real filesystem matches are all excluded by another pattern (e.g.outputs = ["dist/*.js", "!dist/vendor.js"]whenvendor.jsis the only match) is indistinguishable from a pattern that genuinely produced nothing.
src/task/task_cache.rs#L390-L427: track a separateglob_matchedflag set on every raw glob hit (before theis_outputfilter), and gate therequire_matches && !glob_matchedbail on that instead — this currently makesstore()hard-bail!with the misleading message"output pattern {output:?} matched no files"for a legitimate exclude-everything-matched config.src/task/task_source_checker.rs#L672-L708: apply the same fix (track raw-glob-match separately from selected-match) socompute_output_hashdoesn't silently returnOk(None)and disable the freshness-hash baseline for that task on every run.🤖 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/task_cache.rs` around lines 390 - 427, In resolve_output_roots, separate raw glob-hit tracking from matcher-selected output tracking: set glob_matched for every filesystem entry before is_output, and use it for the require_matches no-match check while preserving resolved insertion filtering. Apply the same raw-match versus selected-match distinction in src/task/task_source_checker.rs lines 672-708 so compute_output_hash does not return Ok(None) when all raw matches are excluded; update both sites accordingly.
🧹 Nitpick comments (1)
src/task/task_source_checker.rs (1)
612-636: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff
push_dir_entrieswalks fully-excluded subtrees without pruning.
WalkDirrecurses into every descendant regardless ofis_output, relying purely on per-entry filtering. This is correct (it allows re-inclusion at arbitrary depth), but a large excluded subdirectory (e.g. a big cache dir nested in an output) is still fully traversed and stat'd on every hash computation.ignore::WalkBuildersupports override-aware pruning natively, but adopting it here would require ensuring no deeper re-include pattern exists under a to-be-pruned directory — a non-trivial invariant to maintain safely.🤖 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/task_source_checker.rs` around lines 612 - 636, Optimize push_dir_entries to avoid traversing fully excluded subtrees by using ignore::WalkBuilder with the existing Override matcher for override-aware pruning. Preserve support for deeper re-inclusion patterns by pruning only when the matcher can safely prove no descendant may be included; otherwise retain traversal and current file/directory hashing behavior.
🤖 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.
Outside diff comments:
In `@src/task/task_cache.rs`:
- Around line 390-427: In resolve_output_roots, separate raw glob-hit tracking
from matcher-selected output tracking: set glob_matched for every filesystem
entry before is_output, and use it for the require_matches no-match check while
preserving resolved insertion filtering. Apply the same raw-match versus
selected-match distinction in src/task/task_source_checker.rs lines 672-708 so
compute_output_hash does not return Ok(None) when all raw matches are excluded;
update both sites accordingly.
In `@src/task/task_source_checker.rs`:
- Around line 638-670: Update the static_paths handling in the task
source-checking function to evaluate is_output before treating metadata failures
as incomplete outputs. Skip excluded paths without requiring them to exist,
while preserving existing file, directory, and other-entry hashing for selected
paths and returning Ok(None) only when a selected path is missing.
---
Nitpick comments:
In `@src/task/task_source_checker.rs`:
- Around line 612-636: Optimize push_dir_entries to avoid traversing fully
excluded subtrees by using ignore::WalkBuilder with the existing Override
matcher for override-aware pruning. Preserve support for deeper re-inclusion
patterns by pruning only when the matcher can safely prove no descendant may be
included; otherwise retain traversal and current file/directory hashing
behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Central YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: c86f5076-88b8-4dd6-9f97-0202399155fc
📒 Files selected for processing (8)
TASK_CACHE_PARITY.mddocs/tasks/task-configuration.mde2e/tasks/test_task_artifact_cache_negative_patternse2e/tasks/test_task_artifact_cache_validationschema/mise-task.jsonschema/mise.jsonsrc/task/task_cache.rssrc/task/task_source_checker.rs
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.7.14 x -- echo |
16.5 ± 0.8 | 15.1 | 23.1 | 1.00 |
mise x -- echo |
17.9 ± 1.0 | 16.4 | 24.6 | 1.08 ± 0.08 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.7.14 env |
15.6 ± 0.7 | 14.4 | 20.8 | 1.00 |
mise env |
16.9 ± 0.9 | 15.6 | 23.2 | 1.08 ± 0.07 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.7.14 hook-env |
16.3 ± 0.7 | 15.0 | 22.2 | 1.00 |
mise hook-env |
17.7 ± 1.0 | 16.2 | 23.0 | 1.08 ± 0.08 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.7.14 ls |
14.2 ± 0.7 | 13.0 | 19.1 | 1.00 |
mise ls |
15.6 ± 0.7 | 14.3 | 20.4 | 1.10 ± 0.07 |
xtasks/test/perf
| Command | mise-2026.7.14 | mise | Variance |
|---|---|---|---|
| install (cached) | 150ms | 160ms | -6% |
| ls (cached) | 57ms | 59ms | -3% |
| bin-paths (cached) | 58ms | 60ms | -3% |
| task-ls (cached) | 83ms | 84ms | -1% |

Summary
!exclusions and re-inclusions to task outputs, matching existing source-pattern semanticsTesting
mise run lint-fixcargo test --bin mise output_matcher_excludes_and_reincludes_descendantscargo test --bin mise output_globs_ignore_excludes_and_unescape_literal_bangscargo test --bin mise install_preserves_outputs_excluded_from_cachemise run test:e2e e2e/tasks/test_task_artifact_cache*Task artifact caching remains experimental.
This pull request was generated by an AI coding assistant.
Note
Medium Risk
Changes task-cache restore and output hashing behavior; mistakes in exclusion patterns could leave stale local files or skip caching expected artifacts, though validation and e2e tests mitigate this.
Overview
Task
outputsnow support ordered!exclusions and\!escapes, mirroringsources. Excluded paths are skipped for output freshness hashing and are omitted from task-cache archives; on restore, excluded files already under an output directory are preserved instead of wiped.Artifact cache write/restore uses a shared output matcher so only included paths are archived and transactional installs merge back excluded paths from backup. Validation rejects exclusion-only configs, unsafe exclusion bodies, and invalid patterns.
Docs, JSON schema, parity tracker, e2e, and unit tests cover negative patterns and cache behavior.
Reviewed by Cursor Bugbot for commit 6e11c03. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
!patterns (with\!escaping).outputsexclusion semantics and how exclusions interact with freshness checks and cache artifacts.