Skip to content

v2.18.0: Full Windows support and crash-resilient supervision

Choose a tag to compare

@jdx jdx released this 25 Jul 10:53
· 109 commits to main since this release
Immutable release. Only release title and notes can be modified.
a54b7e2

This release brings full Windows support with CI coverage, makes the supervisor resilient to its own crashes by re-adopting or safely reconciling orphaned daemons, and adds a top-level env table, an IDE-friendly project session API, and a --global flag for daemons add.

Highlights

  • pitchfork now runs on Windows with a full CI suite (422 Rust tests and the bats e2e suite) green on Windows runners (#602) — @gaojunran.
  • The supervisor survives its own crashes. A restarted supervisor now verifies process identity before touching orphaned daemons, re-adopts still-running ones by default, and makes daemons that died while unsupervised eligible for their configured retries (#632, #633, #657, #659) — @jdx.

Added

  • Full Windows support (#602) — @gaojunran. pitchfork now works on Windows, using named pipes for IPC, taskkill /F /T for whole-tree process termination, and cross-platform path/glob handling for file watching. A dedicated Windows CI workflow runs the Rust and bats test suites (only PTY and POSIX-signal features are platform-impossible and skipped).

  • Top-level [env] with Tera templating (#618) — @gaojunran. Define default environment variables for all daemons in one place. Values support templates, and the rendered env is exposed back into the template context so other fields can reference env values. Per-daemon env still wins on key conflicts.

    [env]
    GRAM_HOST = "localhost"
    GRAM_API_PORT = "{{ daemons.api.port }}"
    
    [daemons.api]
    run = "go run ./cmd/api"
    env = { GRAM_HOST = "0.0.0.0" }   # per-daemon overrides the default
  • pitchfork project enter/leave/list for IDE session management (#619) — @gaojunran. A documented, stable API for directory-scoped auto-start/stop without the global shell hook, aimed at IDE and workspace integrations. Sessions are keyed by (pid, dir), so multiple terminals in one project keep daemons alive until the last one leaves, and a single IDE can hold multiple workspaces at once.

    pitchfork project enter --pid PID [--directory DIR]
    pitchfork project leave --pid PID [--directory DIR]
    pitchfork project list [--json]
  • --global flag for daemons add (#621) — @gaojunran. Write a daemon straight to the user-level global config (~/.config/pitchfork/config.toml) under the global namespace, mirroring settings set. It is mutually exclusive with --local / --project.

    pf daemons add api --run 'npm start' --global
  • Orphaned daemons are re-adopted after a supervisor crash (#633) — @jdx. A new supervisor.orphan_policy setting defaults to adopt: after an unclean restart, still-running daemons keep running with their state, resolved ports, and proxy routing intact, and supervision resumes via a poll monitor. Set orphan_policy = "kill" (or PITCHFORK_ORPHAN_POLICY=kill) to restore the previous terminate-on-restart behavior. Note that log capture for an adopted process resumes only on its next restart.

Fixed

  • Orphan cleanup verifies process identity before killing (#632) — @jdx. Daemons now record their kernel start time at spawn, so orphan cleanup identifies a process by PID + start time rather than an unreliable title match. A recycled PID pointing at an unrelated process is never killed — only the stale state entry is reset — and the fix is guarded by new regression tests.

  • Daemons that die during a supervisor crash are retryable again (#657) — @jdx. A daemon that died while its supervisor was down previously stayed silently stopped and never used its configured retry. It now reads errored and comes back on its own, while genuine reboots and intentional supervisor stops are correctly distinguished (via a recorded boot time) and left alone.

  • Child-monitor exit finalization is now atomic (#659) — @jdx. Closed a race where a concurrent start/restart could install a successor while a monitor was writing terminal state, orphaning the live process and silently undoing the restart. Both the child and adopted-daemon monitors now revalidate ownership inside the same lock as the write.

  • Log store tolerates concurrent opens (#660) — @jdx. Opening the SQLite log store from two processes at once no longer fails with database is locked; the WAL journal-mode switch now retries briefly and proceeds regardless, since only the first open ever needs to set it.

  • Web UI loads correctly on nested routes (#603) — @disintegrator. Reloading a nested route like /daemon/:name or /logs/:name no longer renders a blank page with MIME-type errors. A <base href> tag is injected at serve time so relative asset URLs always resolve against the app root (including under a sub-path mount), and missing non-navigation asset requests now return a clear 404.

  • TUI daemons panel shows a shared namespace once (#622) — @qstearns. When every visible daemon shares one namespace (the common single-project case), the namespace is shown once in the panel title (Daemons — <namespace>) and rows render bare daemon names, so the distinguishing name is no longer truncated out of view. Mixed-namespace views keep the per-row namespace/name prefix.

New Contributors

Full Changelog: v2.17.0...v2.18.0

💚 Sponsor pitchfork

pitchfork is maintained by @jdx, an open source developer for entire.io, the title sponsor of the jdx.dev open source tools including mise, aube, hk, and more. Development is sustained by sponsorships.

If pitchfork has a place in your dev workflow, please consider sponsoring at jdx.dev. Individual and company sponsorships are what keep the project healthy and moving forward.