pfp
A fast CLI for managing Prefect 3 deployments and flow runs, built for both human and AI agent use.
What it does
pfp talks directly to the Prefect REST API, bypassing the official CLI's limitations: truncated output, unreliable exit codes, and awkward parameter syntax.
- Substring matching —
pfp run happy-tfindshappy_terraform/happy-terraform-prod - Correct exit codes — 0 for success, 1 for flow failure, 2 for CLI errors
--watchthat works — polls until completion with state change reporting- Dotted path parameters —
--set config.action=destroybuilds nested JSON - Parameter validation — typos caught before submission with "did you mean?" suggestions
--jsonon everything — structured output for programmatic consumption- Full deployment names — no truncation, ever
Installation
From crates.io:
From source:
Configuration
pfp reads your existing Prefect configuration. No extra config files needed.
API URL is resolved from ~/.prefect/profiles.toml:
= "self-hosted"
[]
= "https://prefect.example.com/api"
The PREFECT_API_URL environment variable takes priority if set.
Authentication is optional. If your server requires it, set PREFECT_API_AUTH_STRING with a username:password value — pfp encodes it as HTTP Basic Auth:
Usage
pfp ls
List all deployments:
$ pfp ls
DEPLOYMENT STATUS WORK POOL
happy_ansible/happy-ansible-prod ACTIVE docker-prod
happy_terraform/happy-terraform-prod ACTIVE docker-prod
hello_world/hello_world-dev ACTIVE docker-dev
update_hosts/update_hosts-prod ACTIVE docker-prod
pfp run
Run a deployment by substring match:
Combining --watch with parameters:
$ pfp run happy-t --watch --set config.action=apply --set config.auto_approve=true
Resolved: happy_terraform/happy-terraform-prod
Created flow run 'venomous-alligator' (171a3f55-...)
17:34:00 | Pending
17:34:05 | Running
17:35:27 | Completed
With --watch, the exit code reflects the flow run outcome: 0 for Completed, 1 for Failed/Cancelled/Crashed.
pfp runs
Show recent flow runs for a deployment:
$ pfp runs happy-t
FLOW RUN STATE STARTED DURATION ID
production-apply COMPLETED 2026-02-21 17:34 45s e130c152
production-destroy COMPLETED 2026-02-21 17:34 8s 171a3f55
production-plan COMPLETED 2026-02-21 00:05 3s 7137cfe7
pfp inspect
Fetch one flow run by its full UUID. Unlike pfp runs, this performs an exact
lookup and is not limited to recent runs, so it is safe for automation even
when many runs are created concurrently.
pfp logs
Show logs for a flow run (requires full UUID):
$ pfp logs e130c152-db01-428a-9698-e8404cd2c5d3
2026-02-21T17:34:36 | INFO | Worker submitting flow run 'e130c152-...'
2026-02-21T17:34:41 | INFO | Beginning flow run 'production-apply' for flow 'happy_terraform'
2026-02-21T17:34:41 | INFO | Action: apply
2026-02-21T17:35:27 | INFO | Flow run completed successfully
Get the flow run UUID from pfp runs <query> --json.
pfp pause / pfp resume
pfp cancel
Substring matching
All commands that take a deployment name use unique substring matching against the full flow_name/deployment_name identifier:
| Matches | Behavior |
|---|---|
| 0 | Error: No deployment matching 'query' |
| 1 | Uses the match |
| 2+ | Error: Ambiguous match 'query', candidates: with list |
Use pfp ls to discover available deployment names and find a unique substring.
Parameters
The --set flag builds nested JSON from dotted paths:
Produces:
Values are auto-typed:
| Input | Type |
|---|---|
true / false |
boolean |
42 |
integer |
3.14 |
float |
["a","b"] |
JSON array |
{"k":"v"} |
JSON object |
| anything else | string |
Parameters from --set are merged with the deployment's defaults. Explicit values override defaults.
Validation
Before submitting a flow run, pfp validates --set parameters against the deployment's OpenAPI schema. Typos are caught immediately:
$ pfp run happy-t --set config.dry_urn=true
Error: unknown parameter 'config.dry_urn'
Valid parameters for config:
action, ansible_debug, ansible_limit, ansible_tags,
deployment_name, dry_run, git_ref, inventory_name,
playbook_name, vault_secrets
Did you mean 'config.dry_run'?
Validation is automatic — no flags needed. If a deployment has no schema (older Prefect versions), validation is skipped and parameters are passed through as before.
Params file
For large or deeply-nested parameters — an array of objects, say — that are awkward to express with repeated --set flags, pass the whole parameters object as JSON with --params-file:
|
The payload must be a JSON object matching the deployment's parameters shape:
Precedence is deployment defaults < --params-file < --set, so a single --set config.action=plan can still override one field of a large payload.
The payload is validated against the deployment schema — the same client-side validation as --set — before the run is created. An unreadable file, malformed JSON, or a non-object top-level fails fast with exit code 2 before any API call.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success — command completed, flow run finished (if --watch) |
| 1 | Flow failure — flow run ended in Failed, Cancelled, or Crashed (only with --watch) |
| 2 | CLI error — bad arguments, no match, ambiguous match, API unreachable |
License
MIT