Skip to content

Releases: jdx/pitchfork

v2.22.0: Namespace scoping and worktree auto-discovery

Choose a tag to compare

@jdx jdx released this 18 Aug 10:31
Immutable release. Only release title and notes can be modified.
5e86c84

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 tui and list (#736) — @qstearns. When you work across several projects the dashboard and list mix every namespace together. Both pitchfork tui and pitchfork list now accept a repeatable --namespace <ns> flag (OR logic) to watch a chosen set of namespaces, plus --project to 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 --json all 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. With proxy.enable = true and the default proxy.sync_hosts = true, proxy add and proxy remove deterministically 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 new PITCHFORK_HOSTS_FILE override 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 /proc scans (which can cost tens to hundreds of milliseconds on busy hosts) were previously triggered on every web API list/show request, 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

Choose a tag to compare

@jdx jdx released this 10 Aug 10:53
Immutable release. Only release title and notes can be modified.
f774e7f

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, or restart is run without any daemon argument and stdout is a TTY, pitchfork now opens a fuzzy-filterable multi-select prompt instead of erroring out. start lists all configured daemons (excluding those already running, unless --force), while stop and restart list currently running daemons. Use / to filter, space to toggle, and enter to confirm; daemons are always shown in their qualified namespace/name form. In non-TTY contexts the commands still require explicit daemon IDs. This change also fixes column misalignment in pitchfork ls when 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 reload runs, or a config write occurs, avoiding repeated filesystem traversal, reads, and TOML parsing.

Changed

  • Updated the rmcp MCP SDK to v3 (#725) and clap_usage to v5 (#724).

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

Choose a tag to compare

@jdx jdx released this 02 Aug 18:00
Immutable release. Only release title and notes can be modified.
0a97663

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_output and on_output daemons hand their streams to the dedicated log-sink sidecar, 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 stop waits 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=value field 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_output daemons 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_output hooks are served by the sink (#668) — @jdx. The sink applies the hook's filter/regex and 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.

  • --json flag for supervisor status (#601) — @disintegrator. Emits {"status": "up"} / {"status": "down"} (exit code 0 for pipe-friendly use with jq), reports unknown with 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 (default true, also settable via PITCHFORK_SUPERVISOR_AUTO_START) prevents client commands from spawning an unmanaged supervisor that could win ownership over a systemd- or launchd-managed instance. Explicit supervisor start/run commands 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 both dir = "~/data" and user = "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), so pitchfork usage and 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, stop previously waited only for the group leader to die and returned while children (e.g. docker compose containers) 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, and groups were 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

Choose a tag to compare

@jdx jdx released this 25 Jul 22:16
Immutable release. Only release title and notes can be modified.
da5c119

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 runit runsv/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 using ready_output, an on_output hook, or pty = true continue 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 under orphan_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-initiated stop and 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

Choose a tag to compare

@jdx jdx released this 25 Jul 10:53
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.

v2.17.0: Structured Logs and Readiness Timeouts

Choose a tag to compare

@jdx jdx released this 17 Jul 11:07
Immutable release. Only release title and notes can be modified.
3c7d6a6

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, or jq expression (#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_format setting (auto/json/logfmt/text, with per-daemon overrides in pitchfork.toml) controls parsing, and pitchfork logs gains 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 parsed level, msg, logger, and fields.

  • hl-style structured log highlighting (#592, #595) — @gaojunran. Structured entries render with color-coded level badges, dim logger names, and key=value fields (keys in blue, numbers in green, booleans and null colored) instead of raw JSON. A configurable logs.timestamp_format setting (chrono strftime, default %m-%d %H:%M:%S, also settable via PITCHFORK_LOG_TIMESTAMP_FORMAT) controls the text-mode timestamp. --raw output is now honored even when combined with --jq, and pitchfork 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 for pty = true daemons.

  • Optional overall timeout for readiness checks (#597, closes #545) — @gaojunran. All four check types accept an optional timeout in their object form, using humantime durations. Omitting it preserves the current unbounded polling. When every configured check exhausts its deadline, the daemon is killed and startup fails with exit code 124, so retry, hooks, and depends propagate 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, and ready_output now render through the template pipeline, joining ready_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_port accepts 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 start shorthand (#593) — @gaojunran. When the first argument isn't a known subcommand, it's treated as a daemon id and forwarded to start, so pitchfork api is shorthand for pitchfork start api. All start flags, validation, and help output are reused.

    pitchfork api                  # same as: pitchfork start api
    pitchfork api redis --force

Fixed

  • Global config no longer requires a daemons field (#605) — @senekor. Settings-only global config files (with no daemons) now validate and load correctly, defaulting daemons to an empty set instead of failing.

Breaking Changes

  • ready_port = 0 now fails config parsing at load time, whereas it was previously accepted and produced a TCP check against port 0 (#600). Numeric strings such as ready_port = "8080" are now accepted and normalized.

New Contributors

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

Choose a tag to compare

@jdx jdx released this 09 Jul 15:35
Immutable release. Only release title and notes can be modified.
e99c048

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 logs can now filter messages at the SQLite layer, so history and --tail both 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 --grep values 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, a regexp scalar 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 new append_batch path — 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 copying self.settings, so any read-modify-write cycle silently dropped existing settings. That broke settings set (the new value was discarded) and proxy add / register_namespace / remove_slug (which wiped previously configured settings as a side effect). Settings are now round-tripped through writes, with an is_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_RE was 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.

  • tera upgraded to v2 (#571). The template renderer now registers a compatibility default filter matching v1 semantics (supports value= and boolean=) so existing pitchfork.toml templates keep working.

  • ARM64 Linux release binaries run on older glibc hosts again (#580) — @brandon-julio-t. The aarch64-unknown-linux-gnu release build is now pinned to cross-rs 0.2.5 instead of the mutable :main tag, 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 a GLIBC_2.39 requirement from the newer image.

Changed

  • Faster supervisor startup and liveness checks (#576, fixes #439) — @gaojunran. Procs::new() no longer calls System::new_all() / refresh_processes(), which used to scan every process on the system (~500 ms) on first access — including something as trivial as pitchfork cd checking whether the supervisor is alive. is_running() now uses kill(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.rs and tui/ui.rs divided by 1024 but labeled results as KB/MB/GB. They now use humanbyte 0.4 with matching IEC units (KiB/MiB/GiB in CLI output, compact K/M/G in 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 for memory_limit now advertises ["string", "integer"], matching the deserializer, which already accepted raw byte counts like memory_limit = 52428800.

  • Dependency bumps: ratatui 0.30.2 (#567), xx 2.6.1 (#570).

New Contributors

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

Choose a tag to compare

@jdx jdx released this 01 Jul 22:17
Immutable release. Only release title and notes can be modified.
c8469ac

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), plus PITCHFORK_DAEMON_ID and PITCHFORK_ARCHIVE_REASON (age or count) 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 in pitchfork.toml with cron = "..." but no boot_start never 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 status falls back to config and reports them as available instead of "not found", proxy list matches, and pitchfork clean preserves entries that have a cron_schedule. The /stats web API also gains an available count.

  • 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_logs mixed timestamp-based initial fetch with id-based polling, and the timestamp was captured before IPC started, so last_id stayed at 0 and the next tail(0) returned the entire log history. Both paths now anchor on the daemon's current max row id.

  • Dependency updates: tower-http 0.7 (#558), cron 0.17 (#556), itertools 0.15 (#557).

Documentation

  • $PORTN env 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

Choose a tag to compare

@jdx jdx released this 21 Jun 16:03
Immutable release. Only release title and notes can be modified.
eca6d09

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.

  • --json flag across the CLI (#527) — @gaojunran. list, status, logs, daemons, proxy status, and settings (including settings list / settings get) can now emit structured JSON suitable for scripting. Each command returns command-specific fields, e.g. list includes id, namespace, name, pid, status, disabled, available, proxy_url, error, and port; proxy status includes computed LAN/TLS info and slug entries.

  • logs.timestamp setting and --no-timestamp flag (#526) — @gaojunran. Strip the leading timestamp from pitchfork logs output, useful when piping into tools like lnav or processing JSON log lines. Configure globally via logs.timestamp = false (or PITCHFORK_LOG_TIMESTAMP=false), or per-invocation with pitchfork logs --no-timestamp.

  • Configurable shell for daemon run commands (#531) — @gaojunran. New general.shell setting (default "sh -c") controls how run scripts 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 run in a shell, the tracked PID is the shell process. To make it match your daemon binary, prefix with exec:

    [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 h2 via 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 list table 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: listeners 0.6 (#525), sysinfo 0.39 (#500), rusqlite 0.40 (#498), mdns-sd 0.20 (#497), and vue-router v5 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

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

Choose a tag to compare

@jdx jdx released this 08 Jun 23:10
Immutable release. Only release title and notes can be modified.
e43fd34

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 to pitchfork.jdx.dev. Cargo.toml homepage/documentation, the README links, the JSON Schema URL used in pitchfork.toml, and every miette url(...) diagnostic in src/error.rs have been updated. If you pin the schema in your config, update the directive to point at the new pitchfork.jdx.dev/schema.json URL.

  • Sponsors CLI and docs (#474) — @jdx. pitchfork sponsors and 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_reason and the on_exit fail/clean cases occasionally raced the shell > redirect, asserting on an empty marker file. They now poll until the marker contains the expected PITCHFORK_EXIT_REASON value.

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.