Skip to content

feat: add file watching to auto-restart daemons#165

Merged
jdx merged 9 commits intomainfrom
feat/file-watching
Jan 19, 2026
Merged

feat: add file watching to auto-restart daemons#165
jdx merged 9 commits intomainfrom
feat/file-watching

Conversation

@jdx
Copy link
Owner

@jdx jdx commented Jan 19, 2026

Summary

Add support for a watch config option that allows daemons to be automatically restarted when specified files change.

  • Add watch field to daemon configuration for glob patterns
  • Implement file watching using the existing notify crate infrastructure
  • Auto-restart running daemons when matched files change

Example Config

[daemons.api]
run = "npm run dev"
watch = ["src/**/*.ts", "package.json"]

[daemons.worker]
run = "python worker.py"
watch = ["worker.py", "lib/**/*.py"]

Test Plan

  • cargo build compiles
  • cargo nextest run - all 76 tests pass
  • cargo clippy --all-targets --all-features -- -D warnings - no warnings
  • Manual test: Create a daemon with watch, modify a watched file, verify restart

🤖 Generated with Claude Code


Note

Enables hot-reload by watching files and auto-restarting affected daemons.

  • Config/schema: Add watch array to PitchforkTomlDaemon and JSON schema; CLI config add initializes watch = [].
  • Supervisor: Integrate daemon_file_watch() and restart_watched_daemon() to monitor directories, match changed paths against watch patterns, and restart only running, enabled daemons.
  • File watching utils: New src/watch_files.rs with debounced watcher (notify), glob expansion (glob), and matching (globset), including recursive dir watching and cross-platform path normalization.
  • Docs: New "File Watching" guide; update configuration reference and sidebar; include examples and behavior notes (1s debounce, relative patterns).
  • Deps/CI: Add glob and globset; small release workflow tweak to fetch tags.

Written by Cursor Bugbot for commit 5447aec. This will update automatically on new commits. Configure here.

Copilot AI review requested due to automatic review settings January 19, 2026 15:26
Copy link
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 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 watch field to daemon configuration supporting glob patterns
  • Implemented expand_watch_patterns and path_matches_patterns helper functions for glob matching
  • Added daemon_file_watch method to set up file watching and restart_watched_daemon to 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('*') {
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

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(['*', '?', '[']).

Copilot uses AI. Check for mistakes.

// Spawn the file watcher task
tokio::spawn(async move {
let mut wf = match WatchFiles::new(Duration::from_secs(1)) {
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
let _ = self.stop(id).await;

// Small delay to allow the process to fully stop
time::sleep(Duration::from_millis(100)).await;
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Copy link

@cursor cursor bot left a comment

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.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Copy link

@cursor cursor bot left a comment

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.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Copy link

@cursor cursor bot left a comment

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.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Copy link

@cursor cursor bot left a comment

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.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Copy link

@cursor cursor bot left a comment

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.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

jdx and others added 8 commits January 19, 2026 11:07
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>
@jdx jdx force-pushed the feat/file-watching branch from 003c29f to 0344f36 Compare January 19, 2026 17:08
Copy link

@cursor cursor bot left a comment

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.

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>
@socket-security
Copy link

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedcargo/​glob@​0.3.310010093100100

View full report

@jdx jdx merged commit 1752e74 into main Jan 19, 2026
4 checks passed
@jdx jdx deleted the feat/file-watching branch January 19, 2026 17:48
@jdx jdx mentioned this pull request Jan 19, 2026
jdx added a commit that referenced this pull request Jan 19, 2026
## 🤖 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>
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