feat: add file watching to auto-restart daemons#165
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds file watching functionality to automatically restart daemons when their watched files change. The implementation leverages the existing notify crate infrastructure and adds a watch configuration field for specifying glob patterns.
Changes:
- Added
watchfield to daemon configuration supporting glob patterns - Implemented
expand_watch_patternsandpath_matches_patternshelper functions for glob matching - Added
daemon_file_watchmethod to set up file watching andrestart_watched_daemonto handle automatic restarts
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/pitchfork_toml.rs | Added watch field to PitchforkTomlDaemon struct |
| src/watch_files.rs | Implemented glob pattern expansion and path matching utilities |
| src/supervisor.rs | Added daemon file watching and auto-restart logic |
| src/deps.rs | Added watch field initialization in test helpers |
| src/cli/config/add.rs | Added watch field initialization for new daemon configs |
| tests/test_pitchfork_toml.rs | Added watch field initialization in test |
| Cargo.toml | Added glob crate dependency |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| // Also watch the base directories for patterns with wildcards | ||
| // This ensures we catch new files in watched directories | ||
| if pattern.contains('*') { |
There was a problem hiding this comment.
This check only looks for the * wildcard character, but glob patterns can also contain ? and [...] wildcards. Files matching patterns with these other wildcards won't be detected correctly. Consider checking for all glob wildcard characters: pattern.contains(['*', '?', '[']).
|
|
||
| // Spawn the file watcher task | ||
| tokio::spawn(async move { | ||
| let mut wf = match WatchFiles::new(Duration::from_secs(1)) { |
There was a problem hiding this comment.
The debounce duration of 1 second is hardcoded. Consider extracting this to a configurable value or using the existing interval_duration() helper for consistency with other timing configurations in the codebase.
| let _ = self.stop(id).await; | ||
|
|
||
| // Small delay to allow the process to fully stop | ||
| time::sleep(Duration::from_millis(100)).await; |
There was a problem hiding this comment.
The 100ms delay is a magic number without explanation. Consider extracting this to a named constant (e.g., DAEMON_STOP_GRACE_PERIOD_MS) to make the purpose clearer and easier to adjust if needed.
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.
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.
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.
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.
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.
Add support for a `watch` config option that allows daemons to be automatically restarted when specified files change. Configuration example: ```toml [daemons.api] run = "npm run dev" watch = ["src/**/*.ts", "package.json"] ``` Implementation: - Add `watch` field to PitchforkTomlDaemon for glob patterns - Add `expand_watch_patterns` and `path_matches_patterns` to watch_files.rs - Implement `daemon_file_watch()` in supervisor that: - Collects daemons with watch patterns - Expands glob patterns to directories - Watches directories recursively using notify crate - Restarts running daemons when matched files change Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add new guide at docs/guides/file-watching.md with examples - Document `watch` field in configuration reference - Add file watching to sidebar navigation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The release-plz action creates and pushes the new tag to GitHub, but the local checkout doesn't have it. Add a git fetch --tags step before trying to find the previous tag for release notes generation. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
For non-wildcard patterns like "package.json", always watch the parent directory even if the file doesn't exist yet. This ensures that file watching works correctly when the file is created after the supervisor starts. Previously, if all configured watch patterns were non-existent specific files, file watching would be silently disabled. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
For wildcard patterns like src/**/*.ts, if the base directory (src/) doesn't exist at startup, fall back to watching base_dir instead of silently disabling file watching. This makes wildcard patterns consistent with non-wildcard patterns, which already had this fallback behavior. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
On Windows, path.join() produces backslashes while glob patterns use forward slashes. This caused pattern matching to fail. Now we normalize all paths to use forward slashes before glob matching. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Set require_literal_separator: true so that * only matches within a single directory (standard glob behavior). Use ** for recursive matching across directories. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The glob crate's Pattern::matches_with() doesn't give ** special recursive directory matching semantics. Switch to globset which properly supports ** for matching across directory boundaries. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
003c29f to
0344f36
Compare
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.
Check if a daemon is disabled before restarting it on file changes. This ensures `pitchfork disable` is respected by the file watcher. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
## 🤖 New release * `pitchfork-cli`: 1.0.2 -> 1.1.0 <details><summary><i><b>Changelog</b></i></summary><p> <blockquote> ## [1.1.0](v1.0.2...v1.1.0) - 2026-01-19 ### Added - add file watching to auto-restart daemons ([#165](#165)) - support boolean values for retry configuration ([#170](#170)) - disable web UI by default ([#172](#172)) - auto-generate JSON schema from Rust types ([#167](#167)) ### Fixed - improve cron watcher granularity for sub-minute schedules ([#163](#163)) - improve log file position tracking accuracy ([#164](#164)) </blockquote> </p></details> --- This PR was generated with [release-plz](https://github.com/release-plz/release-plz/). <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Releases `pitchfork-cli` v1.1.0 and syncs version across `Cargo.toml`, `Cargo.lock`, `docs/cli/*`, and `pitchfork.usage.kdl`. > > - **Added**: file watching to auto-restart daemons; boolean support for retry config; web UI disabled by default; JSON schema generation from Rust types > - **Fixed**: improved cron watcher granularity (sub-minute); more accurate log file position tracking > - Updated `CHANGELOG.md` with 1.1.0 notes > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 2e6d4c6. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Summary
Add support for a
watchconfig option that allows daemons to be automatically restarted when specified files change.watchfield to daemon configuration for glob patternsnotifycrate infrastructureExample Config
Test Plan
cargo buildcompilescargo nextest run- all 76 tests passcargo clippy --all-targets --all-features -- -D warnings- no warningswatch, modify a watched file, verify restart🤖 Generated with Claude Code
Note
Enables hot-reload by watching files and auto-restarting affected daemons.
watcharray toPitchforkTomlDaemonand JSON schema; CLIconfig addinitializeswatch = [].daemon_file_watch()andrestart_watched_daemon()to monitor directories, match changed paths againstwatchpatterns, and restart only running, enabled daemons.src/watch_files.rswith debounced watcher (notify), glob expansion (glob), and matching (globset), including recursive dir watching and cross-platform path normalization.globandglobset; small release workflow tweak to fetch tags.Written by Cursor Bugbot for commit 5447aec. This will update automatically on new commits. Configure here.