Skip to content

fix: handle recursive watcher ignore paths correctly#13756

Open
ManManavadaria wants to merge 2 commits intodocker:mainfrom
ManManavadaria:13750-watch-ignore-fix
Open

fix: handle recursive watcher ignore paths correctly#13756
ManManavadaria wants to merge 2 commits intodocker:mainfrom
ManManavadaria:13750-watch-ignore-fix

Conversation

@ManManavadaria
Copy link
Copy Markdown

What I did
Applied ignore rules at the watcher layer and updated recursive traversal to skip ignored files/directories, ensuring permission errors from paths in the ignore list do not fail watch.

Related issue
fixes #13750

(not mandatory) A picture of a cute animal, if possible in relation to what you did

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR aims to fix Docker Compose watch startup failures when an ignored path is unreadable by pushing ignore handling down into the watcher layer. In the codebase, that primarily affects how pkg/compose constructs file watchers and how pkg/watch traverses directories on different platforms.

Changes:

  • Added an ignore matcher parameter to the watcher API and threaded it through Compose watch setup.
  • Updated the naive watcher to skip ignored directories during recursive traversal and to suppress permission errors for ignored paths during startup walks.
  • Adjusted Darwin watcher and test fixture construction to use the new watcher signature.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
pkg/watch/watcher_naive.go Adds ignore-aware traversal and permission handling to the non-fsevents watcher implementation.
pkg/watch/watcher_darwin.go Updates the Darwin watcher constructor signature to match the new watcher API.
pkg/watch/notify_test.go Updates test fixture setup to call NewWatcher with an ignore matcher.
pkg/watch/notify.go Extends the exported watcher constructor to accept an ignore matcher.
pkg/compose/watch.go Builds ignore matchers from watch triggers and passes them into watcher creation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/watch/watcher_naive.go Outdated
Comment thread pkg/watch/watcher_naive.go
Comment thread pkg/compose/watch.go
Comment thread pkg/watch/watcher_darwin.go Outdated
Comment thread pkg/watch/watcher_naive.go Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented May 4, 2026

Codecov Report

❌ Patch coverage is 58.06452% with 13 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
pkg/watch/watcher_naive.go 47.61% 8 Missing and 3 partials ⚠️
pkg/compose/watch.go 75.00% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@ManManavadaria ManManavadaria force-pushed the 13750-watch-ignore-fix branch from 1e22d66 to bba54a8 Compare May 6, 2026 19:15
@ManManavadaria
Copy link
Copy Markdown
Author

Hi @glours @ndeloof ,
I’ve addressed all the review feedbacks in the latest commit. please review it, appreciate your time and feedback.

Signed-off-by: ManManavadaria <manmanavadaria@gmail.com>
…nd apply review fixes

Signed-off-by: ManManavadaria <manmanavadaria@gmail.com>
@ManManavadaria ManManavadaria force-pushed the 13750-watch-ignore-fix branch from bba54a8 to 4ab735b Compare May 7, 2026 13:18
@glours
Copy link
Copy Markdown
Contributor

glours commented May 7, 2026

/review

Copy link
Copy Markdown

@docker-agent docker-agent Bot left a comment

Choose a reason for hiding this comment

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

Assessment: 🟡 NEEDS ATTENTION

This PR correctly applies ignore rules to the watcher layer for Linux/Windows (naive watcher) and handles permission errors from ignored paths during recursive directory traversal. However, there are two issues worth addressing:

  1. macOS (Darwin) — the ignore parameter is silently discarded, so the fix does not apply on macOS.
  2. Multi-trigger ignore OR-logic — merging all trigger ignore matchers with OR logic at the watcher layer can cause directories needed by one trigger to be skipped because another trigger ignores them.

Reviewed 6 files: pkg/compose/watch.go, pkg/watch/notify.go, pkg/watch/notify_test.go, pkg/watch/watcher_darwin.go, pkg/watch/watcher_naive.go, pkg/watch/watcher_naive_test.go

}

func newWatcher(paths []string) (Notify, error) {
func newWatcher(paths []string, _ ...PathMatcher) (Notify, error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM] Darwin watcher silently discards the ignore PathMatcher — ignore rules not applied on macOS

newWatcher on Darwin uses _ ...PathMatcher (variadic, discarded), so the ignore matcher passed from pkg/compose/watch.go is never stored or used. On macOS:

  • Permission errors on paths in the ignore list will still surface as hard errors, defeating the purpose of this PR.
  • No directories will be skipped via shouldSkipDir at the watcher layer; the naive (Linux/Windows) watcher stores and uses d.ignore for both shouldIgnore and shouldSkipDir, but the Darwin path has no equivalent.

The fix should store the matcher in fseventNotify and implement analogous shouldIgnore/shouldSkipDir logic, or at minimum handle permission errors on ignored paths in the Darwin event loop.

Comment thread pkg/compose/watch.go
}

watcher, err := watch.NewWatcher(paths)
watcher, err := watch.NewWatcher(paths, watch.NewCompositeMatcher(ignoreMatchers...))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM] OR-logic composite ignore matcher at watcher layer may suppress directory watching for one trigger due to another trigger's ignore rules

All trigger ignore matchers are merged into a single CompositePathMatcher with OR logic before being passed to NewWatcher. Since CompositePathMatcher.Matches returns true on the first matcher that matches, trigger A's ignore patterns (e.g., dist/) could cause a directory that trigger B legitimately needs to watch to be skipped at the watcher layer entirely.

Example: Service A ignores shared/dist/, Service B watches shared/ without ignoring dist/. At the watcher layer, shared/dist/ is skipped for both because trigger A's ignore rule matches — B will never receive events from shared/dist/.

The per-trigger watchRule.Matches() logic does apply per-trigger ignores correctly for event routing, but the upstream watcher-layer skip prevents events from ever being generated for those directories.

Consider building a per-path/per-trigger watcher or using an intersection (AND) approach — only skip a path if all trigger matchers that cover that path agree it should be ignored.

Comment on lines +294 to +297
if matches {
return true
}
return false
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.

Suggested change
if matches {
return true
}
return false
return matches

Comment thread pkg/watch/watcher_naive.go
Comment on lines +188 to +191
if os.IsPermission(err) && d.shouldIgnore(path) {
logrus.Debugf("Ignoring path: %s", path)
return filepath.SkipDir
}
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.

Same this is already catch by block lines 204-206

Comment on lines +300 to +309
func (d *naiveNotify) shouldIgnore(path string) bool {
if d.ignore == nil {
return false
}
matches, err := d.ignore.Matches(path)
if err != nil {
logrus.Debugf("error checking ignored path %q: %v", path, err)
return false
}
return matches
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.

This function isn't needed, it's only called from the 2 previous unnecessary check blocks above

}

func newWatcher(paths []string) (Notify, error) {
func newWatcher(paths []string, _ ...PathMatcher) (Notify, error) {
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.

This is inconsistent with the naive implementation which use _ PathMatcher

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.

[BUG] watch tries to read ignored folders leading to permission denied

3 participants