Skip to content

fix: watch cwds of dependencies#10054

Merged
jdx merged 12 commits into
jdx:mainfrom
43081j:watch-cwds
Jun 4, 2026
Merged

fix: watch cwds of dependencies#10054
jdx merged 12 commits into
jdx:mainfrom
43081j:watch-cwds

Conversation

@43081j

@43081j 43081j commented May 23, 2026

Copy link
Copy Markdown
Contributor

When I changed watch mode to also watch dependencies' sources, I didn't
take into account the fact that the sources will be used as-is (i.e.
not resolved to anything).

This meant the following would happen:

  • build:a has ["src/*.ts"]
  • build:b has ["lib/*.js"] and depends on build:a
  • We pass ["src/*.ts", "lib/*.js"] to watchexec in the directory of
    build:b

This made lib/*.js basically no-op, or worse, watch the wrong files.

This change resolves the paths to their owning directory so we end up
with ["wherever-builda-lives/src/*.ts", "wherever-buildb-lives/lib/*.js"].

Notable Changes:

  • Instead of passing relative globs to watchexec, we now pass resolved
    ones (relative to the root)
  • We now pass --project-origin to watchexec which comes with some perf
    gains but also means globs are now relative to it
  • We pass --watch {cwd} for the cwd of each dependency
  • This new resolve_source function is basically turning a source glob
    into a relative-to-the-root glob while retaining negations
  • The new common_ancestor function tries to find the common ancestor of two directories

cc @jdx i don't usually delve into rust so i'd love some help here if you can.

especially if there's a better way to find the common ancestor, or if you think we should deal with that differently.

basically, watchexec doesn't seem to support absolute paths, so we have to set the project-origin to something above all globs. which is why this new function tries to find a common root (if not the configured one)

Summary by CodeRabbit

  • Bug Fixes

    • Watch now reliably detects changes for source patterns outside a task’s working directory; out-of-tree patterns trigger rebuilds and watched directories cover relevant files.
  • Improvements

    • Watch generates accurate file filters and recursive watch directories so file-change matching is correct across varied path patterns and anchors.
  • Tests

    • Added end-to-end regression test plus unit tests covering path relativization, filter generation, and watch-directory derivation.

43081j added 4 commits May 22, 2026 09:41
When I changed watch mode to also watch dependencies' sources, I didn't
take into account the fact that the `sources` will be used _as-is_ (i.e.
not resolved to anything).

This meant the following would happen:

- `build:a` has `["src/*.ts"]`
- `build:b` has `["lib/*.js"]` and depends on `build:a`
- We pass `["src/*.ts", "lib/*.js"]` to watchexec _in the directory of
  `build:b`_

This made `lib/*.js` basically no-op, or worse, watch the wrong files.

This change resolves the paths to their owning directory so we end up
with `["wherever-builda-lives/src/*.ts",
"wherever-buildb-lives/lib/*.js"]`.

**Notable Changes:**

- Instead of passing relative globs to watchexec, we now pass resolved
  ones (relative to the root)
- We now pass `--project-origin` to watchexec which comes with some perf
  gains but also means globs are now relative to it
- We pass `--watch {cwd}` for the cwd of each dependency
- This new `resolve_source` function is basically turning a source glob
  into a relative-to-the-root glob while retaining negations

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces logic to calculate a "filter anchor" for watchexec by determining the common ancestor of task working directories, ensuring glob filters are correctly interpreted relative to a project origin. It adds utility functions for finding common path ancestors and resolving source patterns, including support for negations and escaped characters. Review feedback focused on optimizing the common_ancestor function by using AsRef to avoid unnecessary cloning and reducing memory allocations during path component comparisons.

Comment thread src/cli/watch.rs Outdated
Comment thread src/cli/watch.rs Outdated
@greptile-apps

greptile-apps Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a regression introduced when watch mode was extended to track dependency sources: source globs were passed verbatim to watchexec in the wrong working directory, causing out-of-cwd patterns like ../../shared/src/*.ts to match the wrong files (or nothing at all).

  • parse_source + normalize_path resolve each source glob to an absolute path relative to its owning task's cwd. common_ancestor is then called over both task cwds and the resolved source paths, so --project-origin is widened to cover any source that escapes its task's directory.
  • source_watch_dir extracts the non-glob prefix of each source path and adds it as an explicit --watch target when it falls outside every task cwd, ensuring watchexec actually monitors the relevant directories.
  • A new e2e regression test (test_task_watch_cwd_escape) validates the full flow end-to-end against the exact failing scenario described in the PR.

Confidence Score: 4/5

Safe to merge; the core path-resolution logic is correct and well unit-tested, and the one minor gap does not affect correctness.

The anchor computation correctly chains both task cwds and resolved source absolute paths into common_ancestor, so --project-origin always covers out-of-cwd sources. The watch_dirs coverage check has a one-sided heuristic that can produce redundant --watch arguments for source dirs that are parents of a task cwd, but this is harmless for correctness.

The watch_dirs coverage check in src/cli/watch.rs around line 259 could be tightened to also skip directories that already cover a watched cwd.

Important Files Changed

Filename Overview
src/cli/watch.rs Core fix: anchor computation now chains both task cwds and resolved source paths into common_ancestor, correctly widening project-origin to cover out-of-cwd sources. New helpers are well-tested. One minor gap: source dirs that are parents of a task cwd bypass the skip heuristic and add a redundant --watch arg, though filter correctness is unaffected.
e2e/tasks/test_task_watch_cwd_escape New e2e regression test for the cwd-escape scenario. Exercises the exact bug (task dir = packages/foo, source = ../../shared/src/*.ts). Well structured with clear failure messages.

Reviews (9): Last reviewed commit: "chore: formatting" | Re-trigger Greptile

Comment thread src/cli/watch.rs
Comment thread src/cli/watch.rs Outdated
Comment thread src/cli/watch.rs

jdx commented May 29, 2026

Copy link
Copy Markdown
Owner

This mostly looks good, but I think there is one missed case before merge.

When a source escapes the task cwd, the filter anchor is widened correctly, but the watched paths are still only the task cwds. For example:

[tasks.build]
dir = "packages/foo"
sources = ["../../shared/src/*.ts"]

This becomes roughly:

--project-origin /repo
--watch /repo/packages/foo
-f shared/src/*.ts

The filter now points at /repo/shared/src/*.ts, but watchexec is not watching /repo/shared, so changes there may not trigger.

Could we either watch the computed anchor/common ancestor, or add watch dirs for source paths that fall outside their task cwd? A regression test for dir plus ../.. sources would be great too.

This comment was generated by an AI coding assistant.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ed70f8a0-7050-459a-91eb-da496b70f0c9

📥 Commits

Reviewing files that changed from the base of the PR and between 7f000fd and eeabdf5.

📒 Files selected for processing (1)
  • src/cli/watch.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/cli/watch.rs

📝 Walkthrough

Walkthrough

This PR fixes mise watch to properly handle task sources that escape the task's working directory using .. patterns. It computes a filter anchor (common ancestor) from task CWDs and sources, relativizes sources to that anchor, and adds --project-origin to Watchexec so glob filters are interpreted correctly, preventing out-of-tree source changes from being silently dropped.

Changes

Watch filter anchoring for escaped sources

Layer / File(s) Summary
E2E regression test for escaped sources
e2e/tasks/test_task_watch_cwd_escape
New test validates mise watch re-runs tasks when source files outside the task CWD change. Task has dir = "packages/foo" and sources escape via ../../shared/src/*.ts.
Watch command filtering and anchor computation
src/cli/watch.rs (imports, core logic)
Replaces source-merging with anchor-aware filter generation: collects task CWDs, pre-resolves and normalizes sources to absolute paths, computes a widened common-ancestor anchor, relativizes sources to the anchor to produce watchexec --filter/--ignore args, conditionally adds --project-origin, and derives extra recursive --watch directories while avoiding duplicates.
Path anchoring and source processing helpers
src/cli/watch.rs (utility functions)
New private functions: common_ancestor finds longest shared prefix, parse_source classifies/absolutizes sources (handling ..), normalize_path resolves ./.. components without filesystem access, source_watch_dir extracts the watch directory up to glob markers/**, and relativize_source converts absolute sources into anchor-relative watchexec filters preserving literal-bang semantics.
Unit tests for helpers and integration
src/cli/watch.rs (test module)
Adds resolve_source test helper and unit tests validating relativization and literal-bang escaping rules, common_ancestor edge cases, normalize_path parent-component handling, parse_source absolutization with .., anchor widening for sources outside task CWDs, and source_watch_dir glob/**/file-path behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 A rabbit hops through paths unknown,
Anchors found where stray .. have flown,
Filters now match distant files with care,
The watcher wakes and answers every scare.
Tests nudge doors that once stayed closed—hip, hop, hooray!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title refers to a specific code change (watching dependency cwds), but the actual primary changes involve resolving source globs, computing anchors, and adding watch directories for out-of-cwd sources—only partially related to the title.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@43081j

43081j commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

i've added logic to do the latter - basically slice the source dirs upto the first glob character and watch that.

so ../../src/foo/*.ts becomes /wherever/src/foo as a watch dir

and i added an e2e test similar to the existing ones but with a ../ source

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

Actionable comments posted: 1

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

Inline comments:
In `@e2e/tasks/test_task_watch_cwd_escape`:
- Around line 38-44: Replace the fixed "sleep 2" delay with the same bounded
polling loop used earlier in this test: repeatedly check for the "built" marker
in LOG_FILE (using grep -q "built" "$LOG_FILE") with a short interval and a
total timeout, and call fail with the current log contents if the timeout
elapses; remove the literal sleep 2, use the same poll/timeout variables and
loop structure used above in this file to ensure deterministic wait behavior for
the rerun assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 605b48de-4cb7-4a5d-ab74-44941b5b438f

📥 Commits

Reviewing files that changed from the base of the PR and between 53cd329 and a9f4248.

📒 Files selected for processing (2)
  • e2e/tasks/test_task_watch_cwd_escape
  • src/cli/watch.rs

Comment thread e2e/tasks/test_task_watch_cwd_escape
43081j and others added 2 commits June 1, 2026 16:53
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@greptile-apps

greptile-apps Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Want your agent to iterate on Greptile's feedback? Try greploops.

@43081j

43081j commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

@jdx this should be good to go now 🎉

@jdx jdx merged commit 2741b15 into jdx:main Jun 4, 2026
33 checks passed
@43081j 43081j deleted the watch-cwds branch June 4, 2026 16:47
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.

2 participants