Skip to content

feat(task): support negative output patterns - #11367

Merged
jdx merged 3 commits into
mainfrom
agent/task-cache-negative-patterns
Jul 27, 2026
Merged

jdx merged 3 commits into
mainfrom
agent/task-cache-negative-patterns

Conversation

@jdx

@jdx jdx commented Jul 27, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • apply ordered ! exclusions and re-inclusions to task outputs, matching existing source-pattern semantics
  • omit excluded outputs from freshness hashes and artifact archives while preserving existing excluded files during transactional restores
  • validate unsafe and exclusion-only cache output declarations, document the behavior, and update the cache-parity tracker

Testing

  • mise run lint-fix
  • cargo test --bin mise output_matcher_excludes_and_reincludes_descendants
  • cargo test --bin mise output_globs_ignore_excludes_and_unescape_literal_bangs
  • cargo test --bin mise install_preserves_outputs_excluded_from_cache
  • mise 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 outputs now support ordered ! exclusions and \! escapes, mirroring sources. 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

  • New Features
    • Added support for excluding files and directories from task outputs using ! patterns (with \! escaping).
    • Task caching and cache restore now respect output include/exclude rules: excluded outputs stay local but aren’t stored/restored from cache.
    • Output freshness and cache invalidation now correctly consider only included outputs.
    • Improved validation for output-cache restrictions when exclusions are present.
  • Documentation
    • Expanded guidance on outputs exclusion semantics and how exclusions interact with freshness checks and cache artifacts.
  • Tests
    • Added/extended e2e coverage for negative caching/output exclusion cases and invalid output configurations.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Output 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.

Changes

Output Cache Parity

Layer / File(s) Summary
Output matching and freshness
src/task/task_source_checker.rs
Output matchers govern output hashing and last-modified checks, including exclusions, reinclusions, descendants, and escaped ! patterns.
Cache validation and output roots
src/task/task_cache.rs, e2e/tasks/test_task_artifact_cache_validation
Cache configuration validates output patterns and resolves roots using matcher-selected paths, rejecting all-excluded configurations and escaping paths.
Filtered archive and restore
src/task/task_cache.rs
Archives include only selected outputs, while restores preserve excluded existing files and metadata.
Documentation and end-to-end coverage
docs/tasks/task-configuration.md, schema/mise*.json, e2e/tasks/test_task_artifact_cache_negative_patterns, TASK_CACHE_PARITY.md
Documentation, schemas, parity tracking, and end-to-end tests describe and verify negative output-pattern behavior.

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
Loading

Possibly related PRs

  • jdx/mise#11328 — Introduces the experimental local artifact caching that this change extends with output-pattern filtering.
  • jdx/mise#11315 — Modifies adjacent output freshness and hashing logic in task_source_checker.rs.
  • jdx/mise#11356 — Changes overlapping task_source_checker.rs pattern and last-modified handling.

Poem

I’m a rabbit with caches tucked neatly away,
Excluding the files that should stay.
Matchers hop through each path in the queue,
Restoring what’s local and archiving what’s true.
Freshness now follows the patterns we drew!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 matches the main change: adding support for negative task output patterns.

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.

@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 1 potential issue.

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 8a2c790. Configure here.

Comment thread src/task/task_source_checker.rs Resolved
@greptile-apps

greptile-apps Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds ordered negative output patterns to task freshness and artifact caching.

  • Applies output exclusions and later re-inclusions consistently during freshness hashing and archive creation.
  • Preserves existing excluded files during transactional cache restoration.
  • Validates exclusion-only and unsafe cache output declarations.
  • Adds documentation, schemas, unit tests, and end-to-end coverage for the new behavior.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains within the scope of the available previous review threads.

Important Files Changed

Filename Overview
src/task/task_cache.rs Adds exclusion-aware cache validation, root resolution, archive filtering, and preservation of excluded local outputs during transactional restores.
src/task/task_source_checker.rs Introduces ordered output matching and applies it consistently to freshness hashes, modification-time checks, and missing-output handling.
e2e/tasks/test_task_artifact_cache_negative_patterns Exercises excluded inputs and outputs, preservation during restoration, archive omission, and cache invalidation by included inputs.
e2e/tasks/test_task_artifact_cache_validation Covers unsafe exclusion bodies and invalid exclusion-only cache output declarations.
docs/tasks/task-configuration.md Documents ordered exclusions, re-inclusions, literal-bang escaping, cache omission, and restore preservation semantics.

Reviews (3): Last reviewed commit: "fix(task): handle excluded output edge c..." | Re-trigger Greptile

@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.

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 win

Excluded-but-missing static output incorrectly aborts the whole hash.

For each static_paths entry, 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_roots in task_cache.rs handles the analogous case correctly (it checks is_output before requiring existence); this function should do the same, e.g. by checking is_output first 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 win

Glob-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 set true inside the is_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"] when vendor.js is the only match) is indistinguishable from a pattern that genuinely produced nothing.

  • src/task/task_cache.rs#L390-L427: track a separate glob_matched flag set on every raw glob hit (before the is_output filter), and gate the require_matches && !glob_matched bail on that instead — this currently makes store() 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) so compute_output_hash doesn't silently return Ok(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_entries walks fully-excluded subtrees without pruning.

WalkDir recurses into every descendant regardless of is_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::WalkBuilder supports 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4c32d0f and 8a2c790.

📒 Files selected for processing (8)
  • TASK_CACHE_PARITY.md
  • docs/tasks/task-configuration.md
  • e2e/tasks/test_task_artifact_cache_negative_patterns
  • e2e/tasks/test_task_artifact_cache_validation
  • schema/mise-task.json
  • schema/mise.json
  • src/task/task_cache.rs
  • src/task/task_source_checker.rs

@jdx
jdx enabled auto-merge (squash) July 27, 2026 04:37
@github-actions

github-actions Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

Hyperfine Performance

mise x -- echo

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%

@jdx
jdx merged commit bfbc880 into main Jul 27, 2026
35 checks passed
@jdx
jdx deleted the agent/task-cache-negative-patterns branch July 27, 2026 06:03
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