Releases: jdx/pitchfork
Release list
v2.22.0: Namespace scoping and worktree auto-discovery
This release adds namespace scoping to the TUI and pitchfork list, auto-discovers daemons defined in git worktrees and jj workspaces, and fixes a proxy self-deadlock that could wedge every pitchfork command on the machine.
Added
-
Namespace scoping for
tuiandlist(#736) — @qstearns. When you work across several projects the dashboard and list mix every namespace together. Bothpitchfork tuiandpitchfork listnow accept a repeatable--namespace <ns>flag (OR logic) to watch a chosen set of namespaces, plus--projectto scope to the current directory's namespace (resolved the same way short daemon IDs are). Scoping happens as the list is built, so fuzzy search (/), sorting, multi-select, batch operations,--status, and--jsonall operate on the already-scoped set. Namespace values are validated, so typos fail loudly instead of silently matching nothing.$ pitchfork tui --project $ pitchfork list --namespace api --namespace web
-
Auto-discovery of daemons in git worktrees and jj workspaces (#737) — @gaojunran. Supervisor background tasks (cron registration,
boot_start, file watch) now discover daemons defined in worktrees whose namespace was never registered in the global[namespaces]table. A config-only cron daemon living in a worktree is auto-registered and triggered without ever being started manually. See the new "Git Worktrees" section in the namespaces docs.
Fixed
-
Proxy self-deadlock on
proxy add/proxy remove(#739) — @qstearns. Withproxy.enable = trueand the defaultproxy.sync_hosts = true,proxy addandproxy removedeterministically deadlocked against themselves while syncing the hosts file, and every other pitchfork command on the machine queued behind the same lock until the hung process was killed. The hosts sync no longer re-reads the global config under the held lock. A newPITCHFORK_HOSTS_FILEoverride lets you redirect the sync away from the system hosts file. This bug affected releases since v2.10.0. -
Command examples now render as code blocks in generated docs (#738) — @gaojunran. Example and output blocks in the CLI docs previously collapsed into a single paragraph with lost column alignment; they now render as proper code blocks. Command behavior is unchanged.
Changed
- Throttled process-table refreshes (#728) — @gaojunran. Full
/procscans (which can cost tens to hundreds of milliseconds on busy hosts) were previously triggered on every web APIlist/showrequest, process-tree lookup, and TUI frame. Display paths now share a 5s TTL cache and run the scan on a blocking worker so async handlers never block, dropping repeat calls from ~38ms to ~58µs. The longer window also smooths reported CPU%, reducing spurious resource-limit kills. Resource enforcement still forces a fresh scan.
New Contributors
Full Changelog: v2.21.0...v2.22.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.
v2.21.0: Interactive daemon picker and faster config parsing
This release adds an interactive multi-select picker when you run start, stop, or restart with no arguments, and speeds up the hot config-parsing path with an mtime-based cache.
Added
-
Interactive daemon selection (#709) — @gaojunran. When
pitchfork start,stop, orrestartis run without any daemon argument and stdout is a TTY, pitchfork now opens a fuzzy-filterable multi-select prompt instead of erroring out.startlists all configured daemons (excluding those already running, unless--force), whilestopandrestartlist currently running daemons. Use/to filter, space to toggle, and enter to confirm; daemons are always shown in their qualifiednamespace/nameform. In non-TTY contexts the commands still require explicit daemon IDs. This change also fixes column misalignment inpitchfork lswhen cells contain colored text. -
mtime-based cache for config parsing (#711) — @gaojunran. Merged config loading is on a hot path invoked frequently by the interval watcher, lifecycle hooks, and autostop checks. Results are now cached per working directory and reused until a source file's modification time changes, an explicit
settings reloadruns, or a config write occurs, avoiding repeated filesystem traversal, reads, and TOML parsing.
Changed
Full Changelog: v2.20.0...v2.21.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.
v2.20.0: Crash-safe logs everywhere, structured log web UI, and safer concurrent start/stop
This release completes the out-of-process log capture story so ready_output and on_output daemons now survive a supervisor crash, brings structured log rendering with filters to the web UI, and fixes two production race conditions in concurrent daemon start/stop. It also adds several supervisor and config quality-of-life improvements around user-aware path expansion, JSON status output, and boot resiliency.
Highlights
- Log capture now survives a supervisor crash for every non-PTY daemon. Following the sink model introduced in 2.19.0, both
ready_outputandon_outputdaemons hand their streams to the dedicatedlog-sinksidecar, so killing the supervisor no longer breaks logging, readiness detection, or output hooks (#667, #668) — @jdx. - Safer daemon orchestration. Concurrent start/stop of many daemons is now deterministic, and
stopwaits for the whole process group to exit before reporting success (#606) — @disintegrator.
Added
-
Structured log display with filters in the web UI (#630) — @gaojunran. The web UI now renders logs like the CLI, with level badges, logger names, and
key=valuefield formatting. A new filter bar supports level, logger (populated from a new loggers endpoint), full-text search with regex and case-sensitivity toggles, jq expressions with autocomplete, and since/until time ranges. Log streams are served as newline-delimited JSON, and invalid regex or jq expressions surface as errors. -
ready_outputdaemons use the out-of-process sink (#667) — @jdx. The sink now matches the readiness pattern itself, persists and flushes the matching line, then reports it to the supervisor. Readiness,on_ready, active-port detection, and deadline logic are all unchanged, but log capture no longer dies with the supervisor. -
on_outputhooks are served by the sink (#668) — @jdx. The sink applies the hook'sfilter/regexand debounce and reports qualifying lines, so a daemon's hook keeps firing through a supervisor crash. Debouncing moves into the process that sees every line, cutting per-line IPC chatter. PTY daemons (pty = true) remain the only in-process, crash-vulnerable path. -
--jsonflag forsupervisor status(#601) — @disintegrator. Emits{"status": "up"}/{"status": "down"}(exit code 0 for pipe-friendly use withjq), reportsunknownwith an error when the process is alive but IPC fails, and includes the web UI's actual bound URL when the server is running.$ pitchfork supervisor status --json { "status": "up", "web_ui": "..." }
-
Disable client auto-start of the supervisor (#678) — @risu729. A new
settings.supervisor.auto_start(defaulttrue, also settable viaPITCHFORK_SUPERVISOR_AUTO_START) prevents client commands from spawning an unmanaged supervisor that could win ownership over a systemd- or launchd-managed instance. Explicitsupervisor start/runcommands still work, and disabled auto-start now yields actionable connection errors. -
Home-relative path expansion in config (#675) — @risu729. Leading
~/~/...components in daemon, slug, and namespace directory paths (and path-valued env overrides) are now expanded to the user's home instead of being treated as literal relative paths, using pitchfork's existing home resolution so elevated invocations retain the original user's home. -
~expands using the daemon's effective user (#708) — @gaojunran. When a daemon sets bothdir = "~/data"anduser = "postgres",~now resolves to that user's home (looked up by username or numeric UID), matching Unix semantics. Falls back to the supervisor's home when no user is configured. -
Auto-heal stale boot registration on startup (#707) — @gaojunran. When a package-manager upgrade changes the binary path, the launchd plist or systemd unit could still point at the old version and fail on next boot. The supervisor now detects a stale registered path and re-registers with the current binary, best-effort and off the startup critical path. Resolves #544.
-
Commands declare their effect on the system (#666) — @jdx. All 47 commands now carry usage
effect=metadata (read/write/destructive), sopitchfork usageand generated docs indicate whether a command inspects, changes, or destroys state. Daemon-launch commands whose effect is really the user's command are deliberately left unclassified.
Fixed
-
Deterministic concurrent start/stop and whole-group stop wait (#606) — @disintegrator. Fixes two production race conditions. Cold-starting many daemons at once could misattribute IPC responses (a "phantom" start failure aborting dependents) because the wire protocol lacks request IDs; each parallel start/stop task now uses its own dedicated IPC connection, making attribution structural rather than timing-dependent. Separately,
stoppreviously waited only for the group leader to die and returned while children (e.g.docker composecontainers) were still shutting down, letting a force-restart attach to a dying group; the wait now requires whole-group emptiness with the existing SIGTERM →stop_timeout→ SIGKILL escalation, plus per-daemon stop locks and background orphan cleanup so the wider stop window doesn't cause duplicate instances or delayed boot. -
Include registries in the config schema (#671) — @risu729.
slugs,namespaces, andgroupswere skipped from the generated JSON schema even though the parser and docs accept them, causing strict validators to reject valid global config. Editors using the published pitchfork schema now recognize these sections.
Full Changelog: v2.19.0...v2.20.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.
v2.19.0: Crash-proof log capture
A small release focused on making logging and orphan handling more robust across supervisor crashes. Daemon output is now captured by a dedicated sibling process so a supervisor crash no longer takes daemons or their logs down with it, and orphan identity checks are tightened to avoid ever acting on a recycled PID.
Added
- Out-of-process log capture (#661) — @jdx. Daemon stdout/stderr capture moves out of the supervisor into a dedicated sibling process (
pitchfork log-sink), following the runitrunsv/log-service model. Previously the supervisor held the read end of every daemon's output pipe, so killing the supervisor left the pipe with no reader and the daemon's next write took SIGPIPE and usually died with it — undermining the orphan re-adoption added in 2.18.0. Now a supervisor crash is invisible to logging: the daemon keeps writing, the sink keeps recording, and nothing is dropped. A sink that dies while its daemon is still monitored is automatically replaced. Daemons usingready_output, anon_outputhook, orpty = truecontinue to use the in-process path since the supervisor itself must read those streams.
Fixed
- Orphan identity now requires a verified process start time (#665) — @jdx. Orphan cleanup previously fell back to comparing process names when a record had no recorded kernel start time, which meant a recycled PID running a common program (
node,nginx,postgres) could be mistaken for a legacy record of a different daemon — and then adopted, or killed underorphan_policy = "kill". Records without a start time on either side are now treated as unverifiable: pitchfork retains their running state and logs a warning rather than acting on the process. The same protection applies to user-initiatedstopand resource-violation kills, which are skipped only when start times provably contradict each other.
Full Changelog: v2.18.0...v2.19.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.
v2.18.0: Full Windows support and crash-resilient supervision
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 /Tfor 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 referenceenvvalues. Per-daemonenvstill 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/listfor 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]
-
--globalflag fordaemons add(#621) — @gaojunran. Write a daemon straight to the user-level global config (~/.config/pitchfork/config.toml) under theglobalnamespace, mirroringsettings 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_policysetting defaults toadopt: 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. Setorphan_policy = "kill"(orPITCHFORK_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
stoppedand never used its configuredretry. It now readserroredand 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/restartcould 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/:nameor/logs/:nameno 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-rownamespace/nameprefix.
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.
v2.17.0: Structured Logs and Readiness Timeouts
This release brings structured log parsing, filtering, and colorized highlighting to pitchfork logs, adds opt-in timeouts for every readiness check type, extends Tera templates to the remaining ready-check fields, and lets you drop the start subcommand for a quick shorthand.
Highlights
- Structured logs land in
pitchfork logs— auto-detected JSON/logfmt parsing, hl-style colorized rendering, and rich filtering by level, field, orjqexpression (#584, #592, #595) — @gaojunran. - Readiness checks get overall timeouts and full template support so slow or hung startups fail cleanly and check fields can reference other daemons' resolved ports (#597, #600).
Added
-
Structured log parsing and filtering (#584) — @gaojunran. pitchfork now understands structured daemon output. A new
logs.log_formatsetting (auto/json/logfmt/text, with per-daemon overrides inpitchfork.toml) controls parsing, andpitchfork logsgains structured filters:pitchfork logs api --level warn # minimum severity: warn and error pitchfork logs api --field status=500 # repeatable KEY=VALUE field match pitchfork logs api --jq '.duration_ms > 100' # jq expression over parsed fields
JSON output (
--json) now includes parsedlevel,msg,logger, andfields. -
hl-style structured log highlighting (#592, #595) — @gaojunran. Structured entries render with color-coded level badges, dim logger names, and
key=valuefields (keys in blue, numbers in green, booleans andnullcolored) instead of raw JSON. A configurablelogs.timestamp_formatsetting (chrono strftime, default%m-%d %H:%M:%S, also settable viaPITCHFORK_LOG_TIMESTAMP_FORMAT) controls the text-mode timestamp.--rawoutput is now honored even when combined with--jq, andpitchfork logs(no daemon named) always shows the daemon id label so you can tell lines apart. PTY control sequences are stripped from all log output to prevent pager corruption forpty = truedaemons. -
Optional overall timeout for readiness checks (#597, closes #545) — @gaojunran. All four check types accept an optional
timeoutin their object form, usinghumantimedurations. Omitting it preserves the current unbounded polling. When every configured check exhausts its deadline, the daemon is killed and startup fails with exit code124, so retry, hooks, anddependspropagate as usual.[daemons.api] run = "node server.js" ready_http = { url = "HOST/health", status = [200], timeout = "30s" } [daemons.cache] run = "redis-server" ready_port = { port = 6379, timeout = "10s" }
-
Tera templates in the remaining ready-check fields (#600) — @disintegrator.
ready_http,ready_port, andready_outputnow render through the template pipeline, joiningready_cmd, so readiness checks can reference other daemons' resolved values:[daemons.worker] ready_http = "HOST:{{ daemons.redis.port }}/health" ready_port = "{{ daemons.redis.port }}" ready_output = "listening on {{ daemons.redis.port }}" depends = ["redis"]
ready_portaccepts a plain integer or a string/template that must resolve to a valid port (1-65535); a template that renders to an invalid port now fails the start with a clear error. -
Implicit
startshorthand (#593) — @gaojunran. When the first argument isn't a known subcommand, it's treated as a daemon id and forwarded tostart, sopitchfork apiis shorthand forpitchfork start api. Allstartflags, validation, and help output are reused.pitchfork api # same as: pitchfork start api pitchfork api redis --force
Fixed
- Global config no longer requires a
daemonsfield (#605) — @senekor. Settings-only global config files (with no daemons) now validate and load correctly, defaultingdaemonsto an empty set instead of failing.
Breaking Changes
ready_port = 0now fails config parsing at load time, whereas it was previously accepted and produced a TCP check against port 0 (#600). Numeric strings such asready_port = "8080"are now accepted and normalized.
New Contributors
- @disintegrator made their first contribution in #600
- @senekor made their first contribution in #605
Full Changelog: v2.16.0...v2.17.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.
v2.16.0: Log filtering and a settings-preserving config write
A focused release: pitchfork logs gains first-class filtering, a nasty config-write bug that silently wiped [settings.*] entries is fixed, and the web log viewer no longer eats the first token of daemon output.
Added
-
Log filtering with
--grep,--regex, and--case-sensitive(#532) — @gaojunran.pitchfork logscan now filter messages at the SQLite layer, so history and--tailboth respect the filter without extra pipes:pitchfork logs api --grep error --grep warn # OR across substrings pitchfork logs api --regex '5\d{2}' --tail # regex, live tail pitchfork logs api --grep Login --case-sensitive
Multiple
--grepvalues are combined with OR. Regex patterns are validated up front so you get a clear CLI error instead of a mid-query SQLite failure. Behind the scenes, aregexpscalar function is registered per connection with an LRU cache so repeated patterns don't recompile per row, and the supervisor now batches log inserts (100 lines / 100 ms) via a newappend_batchpath — with a synchronous flush right before ready-pattern signalling and process exit, so startup-log capture and trailing lines both stay accurate.
Fixed
-
Config writes preserve existing
[settings.*]entries (#575, fixes #574) — @gaojunran.write_unlocked()was constructing the on-disk config without copyingself.settings, so any read-modify-write cycle silently dropped existing settings. That brokesettings set(the new value was discarded) andproxy add/register_namespace/remove_slug(which wiped previously configured settings as a side effect). Settings are now round-tripped through writes, with anis_empty()guard so empty configs don't emit a bare[settings]header. -
Web log viewer no longer drops the first message token or leaks timestamps (#581) — @gaojunran. The web UI's
LOG_PREFIX_REwas modeled on pitchfork's internal<ts> <level> <msg>format and, when applied to arbitrary daemon stdout, silently ate the daemon's first content token (often its own log level) and failed to strip prefixes from indented or blank continuation lines — which caused pitchfork's prepended timestamp to leak into the rendered content. The parser now strips only the fixed<ts>prefix added by the web log API and keeps the message verbatim. -
teraupgraded to v2 (#571). The template renderer now registers a compatibilitydefaultfilter matching v1 semantics (supportsvalue=andboolean=) so existingpitchfork.tomltemplates keep working. -
ARM64 Linux release binaries run on older glibc hosts again (#580) — @brandon-julio-t. The
aarch64-unknown-linux-gnurelease build is now pinned tocross-rs0.2.5 instead of the mutable:maintag, so the resulting binary targets glibc 2.18 and runs on hosts like Amazon Linux 2023 (glibc 2.34). v2.15.0 had silently picked up aGLIBC_2.39requirement from the newer image.
Changed
-
Faster supervisor startup and liveness checks (#576, fixes #439) — @gaojunran.
Procs::new()no longer callsSystem::new_all()/refresh_processes(), which used to scan every process on the system (~500 ms) on first access — including something as trivial aspitchfork cdchecking whether the supervisor is alive.is_running()now useskill(pid, 0)on Unix for an O(1) liveness check independent of the process cache, and seven caller-side refresh calls that existed only to feed the old check have been removed. -
Consistent, correct IEC byte formatting (#582) — @lthiery. Four hand-rolled formatters in
procs.rsandtui/ui.rsdivided by 1024 but labeled results as KB/MB/GB. They now usehumanbyte0.4 with matching IEC units (KiB/MiB/GiBin CLI output, compactK/M/Gin the TUI), values above GiB roll over to TiB/PiB instead of rendering as e.g.1100.0GB, and per-magnitude precision is preserved. The published JSON schema formemory_limitnow advertises["string", "integer"], matching the deserializer, which already accepted raw byte counts likememory_limit = 52428800.
New Contributors
- @brandon-julio-t made their first contribution in #580
- @lthiery made their first contribution in #582
Full Changelog: v2.15.0...v2.16.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.
v2.15.0: Log archiving and cron/log reliability fixes
A small feature release adding a log archive hook so retention no longer means data loss, plus fixes for config-only cron daemons, trailing log lines at daemon exit, and startup log streaming.
Added
-
Log archive hook (#534) — @gaojunran. Before retention prunes old log entries, pitchfork can now hand them off to a user-defined command. The hook receives entries as JSON Lines on stdin (fields:
id,daemon_id,timestamp,message), plusPITCHFORK_DAEMON_IDandPITCHFORK_ARCHIVE_REASON(ageorcount) in the environment. If the hook exits non-zero, the batch is not deleted, so a failed archive destination won't cause data loss.Configure globally in
settings.toml:[logs.archive_hook] command = "gzip -c >> /var/log/pitchfork/archive.jsonl.gz" batch_size = 1000
Or per-daemon in
pitchfork.toml:[daemons.api] archive_hook = "aws s3 cp - s3://my-bucket/pitchfork-logs/"
Fixed
-
Config-only cron daemons now fire without
boot_start(#548, closes #542) — @gaojunran. Previously, a daemon defined inpitchfork.tomlwithcron = "..."but noboot_startnever triggered, because the cron watcher only iterated daemons already in state. The watcher now auto-registers config-only cron daemons at the start of each tick,pitchfork statusfalls back to config and reports them asavailableinstead of "not found",proxy listmatches, andpitchfork cleanpreserves entries that have acron_schedule. The/statsweb API also gains anavailablecount. -
Trailing log lines no longer lost at daemon exit (#540, closes #537) — @gaojunran. The supervisor's
select!loop used to break immediately when the daemon exited, dropping anything still queued in the mpsc channel or OS pipe buffer. There's now a bounded (5s) post-loop drain that flushes remaining output to the log store before state cleanup. -
Startup log streaming no longer replays old logs (#560) — @gaojunran.
stream_startup_logsmixed timestamp-based initial fetch with id-based polling, and the timestamp was captured before IPC started, solast_idstayed at 0 and the nexttail(0)returned the entire log history. Both paths now anchor on the daemon's current max row id. -
Dependency updates:
tower-http0.7 (#558),cron0.17 (#556),itertools0.15 (#557).
Documentation
$PORTNenv vars and template scoping (#563, closes #562) — @gaojunran. Documented$PORT0,$PORT1, … which expose all resolved ports (not just the first, via$PORT) to a daemon, and corrected the note that suggested{{ daemons.xxx.port }}for a daemon's own ports — templates can only reference dependency ports; use the env vars for your own.
Full Changelog: v2.14.0...v2.15.0
💚 Sponsor pitchfork
pitchfork is built by @jdx, who ships developer tools like mise, hk, pitchfork, and more. Development is sustained by sponsorships.
If pitchfork has a place in your dev workflow, please consider sponsoring on GitHub. Individual and company sponsorships are what keep the project healthy and moving forward.
v2.14.0: Shells, statuses, and structured output
A practical release focused on day-to-day ergonomics: filterable daemon lists, machine-readable output across the CLI, a configurable shell for run commands, and fixes for HTTPS proxying and TUI scrolling.
Added
-
pitchfork list --status <STATUS>(#529) — @gaojunran. Filter the daemon list by one or more statuses (running,stopped,waiting,stopping,failed,errored,available,disabled). The flag is repeatable and combines with OR logic:pitchfork ls --status running pitchfork ls --status available --status stopped
Shell completion for daemon IDs is now subcommand-aware —
pitchfork stop <TAB>only suggests running daemons,pitchfork start <TAB>only suggests available/stopped/errored/failed ones, and so on. -
--jsonflag across the CLI (#527) — @gaojunran.list,status,logs,daemons,proxy status, andsettings(includingsettings list/settings get) can now emit structured JSON suitable for scripting. Each command returns command-specific fields, e.g.listincludesid,namespace,name,pid,status,disabled,available,proxy_url,error, andport;proxy statusincludes computed LAN/TLS info and slug entries. -
logs.timestampsetting and--no-timestampflag (#526) — @gaojunran. Strip the leading timestamp frompitchfork logsoutput, useful when piping into tools likelnavor processing JSON log lines. Configure globally vialogs.timestamp = false(orPITCHFORK_LOG_TIMESTAMP=false), or per-invocation withpitchfork logs --no-timestamp. -
Configurable shell for daemon
runcommands (#531) — @gaojunran. Newgeneral.shellsetting (default"sh -c") controls howrunscripts are executed. The run string is now passed verbatim as the final argument to the shell, fixing a previous split→join round-trip that mangled variable expansion and globs.[settings.general] shell = "bash -c" # or for safer defaults: # shell = "sh -o errexit -o pipefail -c"
Because pitchfork wraps
runin a shell, the tracked PID is the shell process. To make it match your daemon binary, prefix withexec:[daemons.api] run = "exec node server.js"
Fixed
-
Proxy: downgrade forwarded requests to HTTP/1.1 (#515) — @iain. Since v2.10.0 the TLS proxy advertised
h2via ALPN, so browsers connected with HTTP/2 and the request was forwarded upstream still tagged HTTP/2 — which HTTP/1.1 backends (e.g. Vite dev server) rejected, producing 502s. The proxy now normalizes forwarded requests to HTTP/1.1. -
TUI: daemon list scrolls with the selection (#519) — @disintegrator. The dashboard table is now rendered as a stateful widget so the viewport follows the selected row when navigating past the visible area.
-
pitchfork listtable layout (#529) — Disabled marker, proxy URL, and error message are merged into a single trailing column with priority-based coloring (error > disabled > proxy), making the table easier to read on narrow terminals. -
Dependency updates with user-visible upstream fixes:
listeners0.6 (#525),sysinfo0.39 (#500),rusqlite0.40 (#498),mdns-sd0.20 (#497), andvue-routerv5 for the web UI (#506).
Documentation
- Added Web UI screenshots to the docs (#528) — @gaojunran.
- Fixed a broken Tera template link in
configuration-templates.md(#520) — @StefanBRas.
New Contributors
- @iain made their first contribution in #515
- @disintegrator made their first contribution in #519
- @StefanBRas made their first contribution in #520
Full Changelog: v2.13.1...v2.14.0
💚 Sponsor pitchfork
pitchfork is built by @jdx, who ships developer tools like mise, hk, pitchfork, and more. Development is sustained by sponsorships.
If pitchfork has a place in your dev workflow, please consider sponsoring on GitHub. Individual and company sponsorships are what keep the project healthy and moving forward.
v2.13.1: Back to jdx
A small housekeeping release that moves pitchfork's home back to jdx/pitchfork and updates docs, schema, and sponsorship links to match.
Changed
-
Docs and metadata rehomed to
jdx(#474) — @jdx. The documentation site moves topitchfork.jdx.dev.Cargo.tomlhomepage/documentation, the README links, the JSON Schema URL used inpitchfork.toml, and every mietteurl(...)diagnostic insrc/error.rshave been updated. If you pin the schema in your config, update the directive to point at the newpitchfork.jdx.dev/schema.jsonURL. -
Sponsors CLI and docs (#474) — @jdx.
pitchfork sponsorsand its generated help/docs now talk about the "jdx project family" and link to github.com/sponsors/jdx. The docs footer/sponsor block were simplified to a single "Sponsor on GitHub" CTA.
Fixed
- Flaky hook exit-reason tests (#474) — @jdx.
test_hook_on_stop_exit_reasonand theon_exitfail/clean cases occasionally raced the shell>redirect, asserting on an empty marker file. They now poll until the marker contains the expectedPITCHFORK_EXIT_REASONvalue.
Full Changelog: v2.13.0...v2.13.1
💚 Sponsor pitchfork
pitchfork is built by @jdx, who ships developer tools like mise, hk, pitchfork, and more. Development is sustained by sponsorships.
If pitchfork has a place in your dev workflow, please consider sponsoring on GitHub. Individual and company sponsorships are what keep the project healthy and moving forward.