Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
knowsuchagency avatar

Pitchfork

  • 1 installs
  • Updated June 16, 2026
  • knowsuchagency/pitchfork-skill

Guides use of pitchfork, a daemon/process manager for running, supervising, and logging background dev services via pitchfork.toml and a shell hook.

About

A skill covering pitchfork, a Rust daemon/process manager for developers that runs and supervises background dev services. A developer uses it to start, stop, and log long-running local processes as a lighter alternative to pm2 or systemd.

  • Manages background dev daemons defined in pitchfork.toml with ready checks
  • Shell hook auto starts/stops daemons on entering/leaving a project directory

Pitchfork by the numbers

  • 1 all-time installs (skills.sh)
  • Ranked #468 of 550 CLI & Terminal skills by installs in the Skillselion catalog
  • Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/knowsuchagency/pitchfork-skill --skill pitchfork

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs1
Last updatedJune 16, 2026
Repositoryknowsuchagency/pitchfork-skill

What it does

Guides use of pitchfork, a daemon/process manager for running, supervising, and logging background dev services via pitchfork.toml and a shell hook.

Files

SKILL.mdMarkdownGitHub ↗

Pitchfork

Pitchfork is a "devilishly good" daemon/process manager for developers, written in Rust by jdx (author of mise). It starts a background service only if it isn't already running (no duplicate processes), auto-restarts on failure with backoff, resolves dependency chains, supports ready checks/file-watching/cron, and can auto start/stop daemons as you cd in and out of a project via a shell hook. Monitor through a terminal TUI or web dashboard, and drive it from AI assistants through a built-in MCP server.

  • Docs: https://pitchfork.jdx.dev
  • Repo: https://github.com/jdx/pitchfork
  • CLI reference: https://pitchfork.jdx.dev/cli.html
  • Config reference: https://pitchfork.jdx.dev/reference/configuration.html
Tip: run pitchfork <command> --help for the exact, version-specific flags on any subcommand.
This skill documents pitchfork ≥ 2.10.

When to reach for pitchfork

  • You keep starting the same dev server twice and leaking orphan processes — pitchfork refuses to

start a daemon that's already running.

  • You want services to come up automatically when you cd into a project and shut down when you

leave (the shell hook).

  • You have a stack (db → cache → api → worker) with startup ordering and readiness gating.
  • You want pm2/foreman/overmind/systemd ergonomics for local dev without the weight.

Installation

mise use -g pitchfork            # Recommended (jdx's own tool)
cargo install pitchfork-cli      # From crates.io
brew install pitchfork           # Homebrew
# or download a prebuilt binary from https://github.com/jdx/pitchfork/releases
pitchfork --version              # Verify

Shell activation (the shell hook — enables auto start/stop)

The shell hook is what makes pitchfork "automatically start daemons when you enter a project directory and stop them when you leave." It hooks your shell's directory-change so that, on each cd, pitchfork reconciles the daemons declared in the nearest pitchfork.toml. Add the activation line to your shell config, then restart your shell:

echo 'eval "$(pitchfork activate bash)"' >> ~/.bashrc                       # Bash
echo 'eval "$(pitchfork activate zsh)"' >> ~/.zshrc                         # Zsh
echo 'pitchfork activate fish | source' >> ~/.config/fish/config.fish       # Fish

Supported shells: bash, zsh, fish. See https://pitchfork.jdx.dev/guides/shell-hook.html.

How the hook behaves (per the shell-hook guide):

  • On entering a directory, daemons whose config includes auto = ["start"] are started.
  • On leaving, daemons with auto = ["stop"] are stopped — but only after a brief delay (so a

quick cd out && cd back doesn't bounce the service), and only once no terminal sessions remain inside the project directory. Open another terminal still cd'd into the project and the service keeps running.

Tab completions (optional)

pitchfork completion bash > ~/.local/share/bash-completion/completions/pitchfork
pitchfork completion zsh  > ~/.zfunc/_pitchfork
pitchfork completion fish > ~/.config/fish/completions/pitchfork.fish

Start at boot (optional)

boot registers the supervisor with the OS init system (launchd on macOS, systemd on Linux), so daemons marked boot_start = true come up at login/startup.

pitchfork boot enable      # User-level (~/Library/LaunchAgents or ~/.config/systemd/user)
sudo pitchfork boot enable # System-level, for all users (/Library/LaunchDaemons or /etc/systemd/system)
pitchfork boot status
pitchfork boot disable

Core commands

pitchfork run <ID> -- <CMD>...   # Run an ad-hoc command in the background under name ID (no toml needed)
pitchfork start [ID...]          # Start daemon(s) defined in pitchfork.toml; -l/-g/-a for local/global/all
pitchfork stop [ID...]           # Stop daemon(s); -a/-l/-g; graceful SIGTERM then SIGKILL
pitchfork restart <ID>           # Stop then start a daemon
pitchfork status <ID>            # Detailed status of one daemon (PID, status)
pitchfork list                   # Table of all daemons + state (alias: ls; --hide-header to omit header)
pitchfork logs [ID...]           # Show/tail logs; omit ID for all daemons
pitchfork wait <ID>              # Block until the daemon STOPS, tailing its logs (exits with its code)
pitchfork enable <ID>            # Allow a daemon to start (undo disable)
pitchfork disable <ID>           # Prevent a daemon from starting/restarting
pitchfork clean                  # Drop stopped/failed entries from the list (alias: c)
pitchfork tui                    # Interactive terminal dashboard

Most commands have short aliases: r run, s start, kill stop, ls list, l logs, w wait, e enable, d disable, c clean, stat status, cfg config, sup supervisor.

run — one-off background daemons (no config file)

run is the fastest way to background a command. The name (<ID>) makes it idempotent and addressable by every other command.

pitchfork run web -- npm run dev               # Background a dev server named "web"
pitchfork run api -- ./server --port 8080
pitchfork run api -f -- ./server               # -f/--force: restart if "api" already running
pitchfork run api --retry 3 -- ./server        # Restart up to 3 times on failure
pitchfork run api -d 5 -- ./server             # Consider ready after a 5s delay (default 3s)
pitchfork run api -o 'Listening on' -- ./server  # Ready when stdout matches this regex
pitchfork run api --http http://localhost:8080/health -- ./server  # Ready on HTTP 2xx
pitchfork run api --port 8080 -- ./server      # Ready when TCP port 8080 is listening
pitchfork run api --cmd 'pg_isready' -- ./server  # Ready when this shell command exits 0
pitchfork run web --expected-port 3000 --bump -- npm run dev  # Find a free port if 3000 is taken
pitchfork run build -q -- ./build.sh           # -q/--quiet: suppress startup log output
Everything after -- is the command and its args, run verbatim — no shell parsing surprises.

start / stop — daemons from pitchfork.toml

pitchfork start api              # Start one daemon defined in pitchfork.toml
pitchfork start api worker       # Start several
pitchfork start -l               # All LOCAL daemons in ./pitchfork.toml (alias --all-local)
pitchfork start -g               # All GLOBAL daemons in ~/.config/pitchfork/config.toml (--all-global)
pitchfork start -a               # All daemons, local and global
pitchfork start api -f           # Force restart if already running
pitchfork start api --port 8080  # Override/add a ready check at start time
pitchfork stop -a                # Stop everything in reverse dependency order

start waits for each daemon's ready check to pass before returning. stop does a graceful shutdown: SIGTERM, wait ~3s (fast polling), then SIGKILL if still alive. With -a/-l/-g, daemons stop in reverse dependency order (dependents before their dependencies).

logs — view and follow output

pitchfork logs api                 # All logs for "api" (paged if long)
pitchfork logs api worker          # Multiple daemons
pitchfork logs                     # All daemons
pitchfork logs api -n 50           # Last 50 lines
pitchfork logs api -f              # Follow in real time (alias --tail/--follow)
pitchfork logs api --since 5min    # Relative window: 5min, 2h, 1d
pitchfork logs api --since '10:30' --until '12:00'   # Time window (today)
pitchfork logs api --since '2024-01-15 10:00:00'     # Absolute datetime
pitchfork logs api --raw -n 100    # Raw lines, no color/formatting (good for piping)
pitchfork logs api --clear         # Delete logs for "api"  (pitchfork logs --clear = all)

Logs are stored under the pitchfork state dir (e.g. ~/.local/state/pitchfork/logs).

wait — block until a daemon stops

wait blocks until the daemon exits, streaming its logs, and returns the daemon's exit code. Use it to chain on a one-shot daemon or keep a script attached to a long-running one:

pitchfork wait migrate && echo "migration done"
Note: wait waits for the daemon to stop. To block until a service is ready and then
continue, use a ready check (--port, --http, --output, --cmd, --delay) on run/start,
which already blocks until ready before returning.

Configuration: pitchfork.toml

Define daemons in a pitchfork.toml at your project root. Each [daemons.<id>] block describes one service. A config.toml in ~/.config/pitchfork/ (or /etc/pitchfork/) holds global daemons available everywhere.

# Optional: prefix this file's daemon names, e.g. referenced as "frontend/web"
namespace = "frontend"

[daemons.postgres]
run = "postgres -D ./data"
auto = ["start", "stop"]        # auto-start on cd-in, auto-stop on cd-out (needs the shell hook)
ready_port = 5432               # ready when this TCP port accepts connections
dir = "db"                      # working directory (relative to the toml, or absolute)

[daemons.redis]
run = "redis-server"
auto = ["start", "stop"]
ready_output = "Ready to accept connections"   # ready when stdout matches this regex

[daemons.api]
run = "npm run dev:api"
depends = ["postgres", "redis"] # started after deps are ready (topological; independents in parallel)
ready_http = "http://localhost:8080/health"    # ready when this URL returns 2xx
retry = 3                       # auto-restart up to 3 times on failure (true = unlimited)
watch = ["src/**/*.ts"]         # restart when matching files change
env = { NODE_ENV = "development", PORT = "8080" }

[daemons.worker]
run = "npm run worker"
depends = ["redis"]
ready_delay = 2                 # just wait N seconds, then consider ready

[daemons.cleanup]
run = "./scripts/cleanup.sh"
cron = "0 0 2 * * *"            # run on a 6-field cron schedule (sec min hour dom mon dow): 2am daily

Daemon keys

KeyTypePurpose
runstring (required)The command to execute.
dirstringWorking directory (relative to the toml, or absolute).
envtableEnvironment variables, e.g. { NODE_ENV = "development" }.
userstringRun as this user/uid (e.g. "postgres" or "501"); needs privileges.
autoarrayShell-hook lifecycle: "start" (on cd-in), "stop" (on cd-out).
dependsarrayDaemons that must be ready first; topological, independents run in parallel. Cross-namespace via "global/postgres".
retryint or boolAuto-restart on failure: a count, or true for unlimited (with backoff).
boot_startboolStart this daemon when the supervisor starts at boot.
miseboolRun the command through mise (load mise.toml tools/env first).
Ready checks(pick one; all block start/run until satisfied)
ready_delayintSeconds to wait, then mark ready.
ready_outputstringRegex; ready when daemon output matches.
ready_httpstring or tableURL that must return 2xx, or { url = "...", status = [200, 401] }.
ready_portintTCP port that must be listening.
ready_cmdstringShell command polled until it exits 0.
File watching
watcharrayGlob patterns; matching changes trigger a restart.
watch_modestring"native", "poll", or "auto" (default).
Ports / proxy
portint / array / tableExpected port(s); table form { expect = [3000], bump = 10 } auto-finds a free port.
Scheduling
cronstring or table6-field cron (sec min hour dom mon dow), or { schedule, retrigger, immediate }.
Resource limits
memory_limitstringe.g. "512MB", "2GiB" — restart/kill if exceeded.
cpu_limitnumberCPU percent cap (100 = one core; 200 = two).
stop_signalstring or tableSignal for graceful stop, e.g. "SIGINT" or { signal = "SIGINT", timeout = "5s" }.
Deprecated port keys you may see in older configs: expected_port, auto_bump_port,
port_bump_attempts. Prefer the unified port key.

Lifecycle hooks — [daemons.<id>.hooks]

Run commands at lifecycle transitions (see https://pitchfork.jdx.dev/guides/lifecycle-hooks.html):

[daemons.api]
run = "npm run dev:api"
ready_http = "http://localhost:8080/health"

[daemons.api.hooks]
on_ready = "curl -X POST https://alerts.example.com/ready"  # daemon passed its ready check
on_fail  = "./scripts/cleanup.sh"                           # daemon failed
on_retry = "echo 'retrying...'"                             # before each retry attempt
on_stop  = "./cleanup.sh"                                   # explicitly stopped by pitchfork
on_exit  = "./teardown.sh"                                  # any termination (clean exit, crash, or stop)
# on_output supports filtering: { run = "...", regex = "ERROR", debounce = "2s" }

Cron scheduling

cron uses a 6-field expression: second minute hour day-of-month month day-of-week (Sunday = 0). The inline-table form adds a retrigger policy:

[daemons.report]
run = "./generate-report.sh"
cron = { schedule = "0 0 2 * * *", retrigger = "finish", immediate = false }

retrigger controls what happens when the schedule fires while the previous run is still active:

ValueBehavior
finish (default)Only retrigger once the previous run has finished — no overlap.
alwaysKill the running instance and start fresh — always run the latest.
successOnly retrigger if the previous run exited 0.
failOnly retrigger if the previous run failed (auto-retry logic).

See https://pitchfork.jdx.dev/guides/scheduling.html.

Groups

Group daemons to start/stop several at once:

[groups.backend]
daemons = ["postgres", "redis", "api", "worker"]
pitchfork start backend     # start the whole group

Managing config from the CLI

config add writes a [daemons.<id>] block into the nearest pitchfork.toml:

pitchfork config                 # List all pitchfork.toml files from the cwd upward
pitchfork config add api -- npm run dev          # Command after --
pitchfork config add api --run 'npm start' --retry 3
pitchfork config add api --run 'npm start' --watch 'src/**/*.ts'
pitchfork config add api --run 'npm start' --autostart --autostop   # sets auto = ["start","stop"]
pitchfork config add worker --run './worker' --depends api
pitchfork config add report --run './report.sh' \
  --cron-schedule '0 0 2 * * *' --cron-retrigger finish
pitchfork config add api --run './server' \
  --ready-http http://localhost:8080/health      # also: --ready-output, --port, --ready-cmd, --delay
pitchfork config add api --run './server' --on-ready 'curl .../ready' --on-fail './cleanup.sh'
pitchfork config remove api      # Remove a daemon (alias: rm)

Namespaced ids are supported, e.g. pitchfork config add frontend/api ....

Auto start/stop (the day-to-day workflow)

With the shell hook installed (pitchfork activate), mark daemons auto = ["start", "stop"]. Entering the project directory starts them; leaving stops them (after a short delay, once no terminals remain in the dir). Services come up exactly when you're working and clean up when you move on — no manual start/stop needed.

[daemons.web]
run = "npm run dev"
auto = ["start", "stop"]
ready_port = 3000

Use auto = ["start"] alone to start on entry but leave it running, or auto = ["stop"] to only clean up on exit.

Reverse proxy — stable *.localhost URLs

The proxy maps a stable slug URL (e.g. https://myapp.localhost) to a daemon's actual port, so bookmarks and configs don't break when a port changes or gets bumped. Slugs live in the global config under [slugs].

pitchfork proxy trust         # Install the proxy's self-signed TLS cert into the system trust store
pitchfork proxy add ...       # Map a slug → project dir + daemon
pitchfork proxy status        # Show all slugs and their state
pitchfork proxy remove ...    # (alias: rm)

Enable it in config:

[settings.proxy]
enable = true

TUI dashboard

pitchfork tui     # Interactive terminal dashboard (start/stop/restart, view logs, vim-style keys)

pitchfork list gives a quick text overview; pitchfork logs -f follows output. A browser-based web dashboard is also available — see https://pitchfork.jdx.dev/guides/tui.html.

Supervisor

A background supervisor process tracks daemons and performs restarts/scheduling/file-watching. The shell hook and boot enable manage it for you, but you can drive it directly:

pitchfork supervisor start    # Start the supervisor in the background
pitchfork supervisor status
pitchfork supervisor stop
pitchfork supervisor run      # Run it in the foreground (debugging)

Container mode

For Docker/CI, pitchfork can run as PID 1 / foreground supervisor that brings up the whole stack and keeps the container alive. See https://pitchfork.jdx.dev/guides/container-mode.html.

MCP server (AI assistants)

Pitchfork ships an MCP server so assistants like Claude Code and Cursor can manage daemons directly:

pitchfork mcp        # Speaks MCP over stdin/stdout; wire into your assistant's MCP config

Example Claude Code registration:

claude mcp add pitchfork -- pitchfork mcp

Exposed tools: pitchfork_status (list daemons + PID/status/errors), pitchfork_start (with force to restart), pitchfork_stop, pitchfork_restart, and pitchfork_logs (n lines, optional daemon ids). See https://pitchfork.jdx.dev/guides/mcp.html.

Quick recipes

Background a dev server and follow it:

pitchfork run web -- npm run dev
pitchfork logs web -f

A full stack with ordering and readiness, auto-managed on cd:

[daemons.db]
run = "docker compose up postgres"
auto = ["start", "stop"]
ready_port = 5432

[daemons.api]
run = "npm run dev:api"
depends = ["db"]
auto = ["start", "stop"]
ready_http = "http://localhost:8080/health"
retry = 3
watch = ["src/**/*.ts"]

[daemons.web]
run = "npm run dev:web"
depends = ["api"]
auto = ["start", "stop"]
ready_port = 3000

Then just cd into the project (with the shell hook active) and everything comes up in order; cd away and it tears down.

Tips & gotchas

  • Pitchfork won't start a daemon that's already running — re-running start/run is safe and

idempotent (it's how you avoid duplicate dev servers).

  • Prefer depends + a ready_* check over manual ordering or sleep — pitchfork blocks on

readiness and parallelizes independent daemons.

  • ready_* blocks until ready; wait blocks until stopped. Don't confuse them.
  • The shell hook only stops a daemon once no terminal sessions remain in the project dir — a

second terminal still in the directory keeps services alive.

  • Cron is 6 fields (leading seconds), unlike classic 5-field crontab.
  • pitchfork clean clears stale stopped/failed rows from list; it never touches running daemons

or your config.

  • Global daemons live in ~/.config/pitchfork/config.toml; reference them across projects as

global/<name> (or your configured namespace) in depends/groups.

  • Run pitchfork <command> --help for exact, version-specific flags.

Related skills

CLI & Terminaldeployinfra

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.