Skip to content

feat(watch): impl poll mode#353

Merged
jdx merged 1 commit into
jdx:mainfrom
gaojunran:feat-watch-poll
Apr 26, 2026
Merged

feat(watch): impl poll mode#353
jdx merged 1 commit into
jdx:mainfrom
gaojunran:feat-watch-poll

Conversation

@gaojunran

Copy link
Copy Markdown
Contributor

addresses #351.

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

Code Review

This pull request introduces configurable file-watching backends, allowing users to choose between platform-native notifications, polling, or an automatic fallback mode. This is particularly useful for environments like network filesystems where native notifications may fail. The changes include updates to the configuration schema, documentation, and a refactor of the internal watcher logic to support multiple backends simultaneously. However, a critical bug was identified in the supervisor's main watch loop: the tokio::select! block will enter a 100% CPU busy loop if any watcher backend is uninitialized, as the branches return immediately instead of pending.

Comment thread src/supervisor/watchers.rs
@greptile-apps

greptile-apps Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR implements per-daemon watch_mode configuration (native | poll | auto) backed by separate WatchFiles instances for each backend, with auto-mode falling back per-directory to polling when native registration fails. The watcher loop, settings, schema, docs, and E2E tests are all updated consistently.

Confidence Score: 5/5

Safe to merge — no correctness or security issues found; all findings are style suggestions.

Only P2 findings: submodule init ordering in mise.toml, runtime vs compile-time enforcement of the Auto-mode restriction in WatchFiles::new, and duplicated match arms. The core watcher loop logic is sound, the auto-fallback state machine handles creation failure and per-dir failure correctly, and watched_native_dirs/watched_poll_dirs are only assigned after target sets are adjusted to reflect what was actually registered.

src/watch_files.rs — WatchMode::Auto runtime error and duplicated match arms; mise.toml — submodule init placement

Important Files Changed

Filename Overview
src/supervisor/watchers.rs Core watcher loop refactored to support separate native/poll WatchFiles instances with auto-fallback per-dir tracking; logic is sound but complex
src/watch_files.rs Adds WatchFilesBackend enum for native/poll dispatch; WatchMode::Auto returns a runtime error instead of compile-time enforcement; match arms are duplicated
src/pitchfork_toml.rs Adds WatchMode enum (Native default, Poll, Auto) with serde/schemars derives; cleanly integrated into PitchforkTomlDaemon and serialization round-trip
src/daemon.rs Adds watch_mode field to Daemon and RunOptions structs with correct Default and clone implementations
mise.toml Adds git submodule init after cargo nextest run but before bats; functional today but could silently break if Rust tests gain submodule dependencies
tests/test_e2e_watch.rs Adds E2E tests for poll and auto watch modes; covers PID change detection, state persistence of watch_mode, and daemon restart on file change
settings.toml Adds watch_poll_interval setting (500ms default) and clarifies watch_interval is for config refresh, not polling scan cadence

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Loop iteration: get_all_watch_configs] --> B[Partition dirs by WatchMode]
    B --> C{required_auto_dirs?}
    C -- yes --> D{dir in auto_fallback_dirs?}
    D -- yes --> E[→ target_poll_dirs]
    D -- no --> F[→ target_native_dirs]
    C -- no --> G[Skip]
    F --> H[unwatch_removed_dirs native]
    H --> I{native_wf exists?}
    I -- no --> J[Create native WatchFiles]
    J -- ok --> K[native_wf = Some]
    J -- err --> L[Move all target_native → target_poll, clear target_native]
    K --> M[watch_new_dirs native, returns new_fallback_dirs]
    I -- yes --> M
    M --> N{fallback dirs?}
    N -- yes --> O[Remove from target_native, Add to target_poll, Record in auto_fallback_dirs]
    O --> P[unwatch_removed_dirs poll]
    L --> P
    E --> P
    P --> Q{poll_wf exists?}
    Q -- no --> R[Create poll WatchFiles]
    R -- ok --> S[poll_wf = Some]
    R -- err --> T[clear target_poll]
    S --> U[watch_new_dirs poll]
    Q -- yes --> U
    U --> V[watched_native = target_native, watched_poll = target_poll]
    T --> V
    V --> W[Prune stale auto_fallback_dirs]
    W --> X{tokio::select!}
    X -- native change --> Y[restart_for_changed_paths]
    X -- poll change --> Z[restart_for_changed_paths]
    X -- watch_interval timeout --> A
Loading

Reviews (11): Last reviewed commit: "feat(watch): impl poll mode" | Re-trigger Greptile

@gaojunran gaojunran force-pushed the feat-watch-poll branch 2 times, most recently from 27accf5 to 0e10e11 Compare April 14, 2026 13:16
Comment thread src/supervisor/watchers.rs Outdated
@gaojunran gaojunran force-pushed the feat-watch-poll branch 2 times, most recently from 0f7fe59 to 1420edc Compare April 16, 2026 05:08
Comment thread src/supervisor/watchers.rs
Comment thread src/supervisor/watchers.rs
Comment thread src/supervisor/watchers.rs
@gaojunran gaojunran force-pushed the feat-watch-poll branch 3 times, most recently from 919f248 to ce91300 Compare April 16, 2026 12:35
@gaojunran gaojunran marked this pull request as ready for review April 26, 2026 06:59
@jdx jdx merged commit 272d750 into jdx:main Apr 26, 2026
5 checks passed
@jdx jdx mentioned this pull request Apr 26, 2026
jdx added a commit that referenced this pull request Apr 26, 2026
## 🤖 New release

* `pitchfork-cli`: 2.6.0 -> 2.7.0

<details><summary><i><b>Changelog</b></i></summary><p>

<blockquote>

## [2.7.0](v2.6.0...v2.7.0) -
2026-04-26

### Added

- *(supervisor)* run daemons as a configured user
([#384](#384))
- *(watch)* impl poll mode
([#353](#353))
- *(cli)* stop / restart --all-global / --all-local
([#385](#385))
- version check in IPC
([#354](#354))

### Fixed

- pass error when failed to parse toml
([#386](#386))

### Other

- *(deps)* update rust crate xx to v2.5.4
([#378](#378))
- *(deps)* lock file maintenance
([#371](#371))
- *(deps)* update rust crate xx to v2.5.4
([#363](#363))
- *(deps)* update rust crate hyper-rustls to v0.27.9
([#359](#359))
- *(deps)* update rust crate rmcp to v1.5.0
([#364](#364))
- *(deps)* update rust crate libc to v0.2.185
([#360](#360))
- *(deps)* update rust crate tokio to v1.52.1
([#365](#365))
- *(deps)* update rust crate uuid to v1.23.1
([#362](#362))
- *(deps)* update rust crate rustls to v0.23.38
([#361](#361))
- *(deps)* update rust crate clap to v4.6.1
([#358](#358))
- *(deps)* update rust crate axum to v0.8.9
([#357](#357))
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk release bookkeeping: only bumps the `pitchfork-cli` version
and regenerates changelog/docs metadata, with no runtime code changes.
> 
> **Overview**
> Updates project metadata for the `v2.7.0` release.
> 
> Bumps `pitchfork-cli` from `2.6.0` to `2.7.0` across `Cargo.toml`,
`Cargo.lock`, and the generated CLI docs (`docs/cli/*` and
`pitchfork.usage.kdl`), and adds the `2.7.0` entry to `CHANGELOG.md`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
e93c44b. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[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