---
name: prefect-cloud-onboarding
description: Onboard a user to Prefect Cloud end-to-end — connect their account, deploy a script from GitHub, run it, schedule it, and set up tested failure/staleness email alerts, driving everything through the prefect-cloud and prefect CLIs. Use when a user wants to get started with Prefect Cloud, deploy or schedule their first workflow, or asks to be onboarded. Not for general Prefect debugging or supporting existing deployments outside this onboarding flow.
---

# Onboard a user to Prefect Cloud

Goal: a working, scheduled, **monitored** workflow — ideally the user's own
code. This document is self-contained;
assume no other Prefect skills, files, or MCP servers are available.

**Escape hatch:** if you hit anything this document doesn't cover, fetch
https://docs.prefect.io/llms.txt — it indexes the full documentation with
links; follow the relevant one rather than guessing.

## Ground rules

- Phases 1–3 are the core; Phase 4 is an optional follow-on. Offer, never force.
- **Never handle credentials.** Login is browser-interactive: the user runs it.
  Never ask for API keys in chat; never write keys to files.
- **Ask before outward-facing actions** (anything that emails real people).
- Prefer the user's real script over the demo.
- Verify each step before the next; if a run fails, show the logs and say so.

## Arriving mid-journey

Not every user starts from zero — check state before walking them through
steps they've already done:

- `uvx prefect-cloud whoami` succeeds → already connected; skip the login
  step in Phase 1.
- `uvx prefect-cloud github ls` shows the target repo → GitHub is already
  set up; skip `github setup`.
- A deployment already exists and runs green → jump straight to Phase 3;
  the safety net stands on its own and is the most valuable part.

## Agent-friendly CLI usage

After login, you drive everything yourself via `uvx prefect-cloud ...`
(onboarding CLI) and `uvx prefect ...` (full CLI, same stored profile).
Nothing is installed globally: bare `prefect` is "command not found" in your
shell **and the user's** — always prefix with `uvx`, and tell the user to do
the same for anything they run themselves. The full CLI targets interactive
terminals — for agent use:

```bash
# ALWAYS pass --no-prompt as a TOP-LEVEL flag to disable confirmations
uvx prefect --no-prompt deployment delete <name>

# Rich tables truncate IDs. Use raw JSON instead:
uvx prefect api POST /flow_runs/filter --data '{"limit": 5}'   # raw REST, JSON out
uvx prefect deployment inspect <flow>/<name> -o json           # single resource

# IDs must be complete UUIDs — take them from JSON, never from tables:
uvx prefect api POST /deployments/filter --data '{"limit": 5}' \
    | python3 -c 'import json,sys; [print(d["id"]) for d in json.load(sys.stdin)]'
```

`prefect api <METHOD> <path>` is a raw passthrough to the Cloud REST API —
use it whenever no purpose-built command fits.

## Phase 0 — Preflight

1. Check `uv --version` (install: https://docs.astral.sh/uv/getting-started/installation/).
2. Ask in one message: (a) Prefect Cloud account? (free:
   https://app.prefect.cloud/auth/sign-in) (b) Where's the script — GitHub
   repo (which repo, which function?) or local file? (c) If GitHub: public or
   private?

## Phase 1 — Connect (the only user-driven step)

Check whether this machine is already connected before asking the user to
do anything:

```bash
uvx prefect-cloud whoami      # success → skip login below
uvx prefect-cloud github ls   # target repo listed → skip github setup below
```

For whatever's missing, have the **user** run in their own terminal:

```bash
uvx prefect-cloud login
uvx prefect-cloud github setup   # code not on GitHub yet? defer — Path B re-runs this once the repo exists
```

Verify API access yourself before promising anything:

```bash
uvx prefect api POST /deployments/filter --data '{"limit": 1}'
```

Auth error → re-run login; org-repo failure in `github setup` is usually a
pending GitHub-app approval by an org admin.

## Phase 2 — Deploy, run, schedule (agent-driven)

### Path A — code on GitHub (preferred)

The entrypoint function needs **no Prefect decorators or imports** — Prefect
wraps plain functions into flows at load time and captures `print()` output.

```bash
# Entrypoint is path-from-repo-root:function_name
uvx prefect-cloud deploy <path/to/script.py:function> \
    --from <account>/<repo> --name onboarding
```

Options as needed (ask, don't guess): `--with <pkg>` /
`--with-requirements <path>`, `--parameter k=v`, `--env K=V`,
`--with-python 3.12`. For `--secret K=<value>`, give the user the command to
run themselves — secret values must not pass through you. Demo fallback:
`uvx prefect-cloud deploy examples/hello.py:hello_world --from PrefectHQ/prefect-cloud --name onboarding`.

```bash
uvx prefect-cloud run <flow_name>/onboarding --follow    # streams logs — surface them
```

Fix and rerun until green (usually a missing dep → redeploy with `--with`, or
a wrong entrypoint path). Never proceed on a red run. Then schedule at a
cadence that fits the job (don't default to hourly):

```bash
uvx prefect-cloud schedule <flow_name>/onboarding "0 6 * * *"
```

`prefect-cloud schedule` attaches the **local timezone of the machine
running the command** to the cron automatically — write the cron in local
time, do NOT convert to UTC. If the user wants a different timezone than
this machine's (e.g. scheduling for a server or team elsewhere), set it
explicitly: `TZ="America/New_York" uvx prefect-cloud schedule ...`. Verify
the attached timezone via `deployment inspect` afterward (other tools, like
the raw API, default to UTC). Calling `schedule` again replaces the existing
schedule rather than stacking a second one.

### Path B — local script, no GitHub repo

Put it on GitHub now — a one-file repo is enough, and Cloud then runs the
code even when their laptop is off. If `gh` is available: `gh repo create` +
push (confirm repo name/visibility with the user first), re-run
`uvx prefect-cloud github setup` for the new repo, then follow Path A. If
the user can't or won't use GitHub, say plainly that this onboarding needs
the code in a repo, and point them at the docs (see the escape hatch) for
self-hosted execution options.

### Verify

Confirm a `Completed` run and report concretely — share the deployment URL
and next scheduled run time from
`uvx prefect deployment inspect <flow>/onboarding -o json`. Inspect the
schedule object itself, not just the next-run timestamp — this is what
catches a wrong timezone or a stale duplicate schedule:

```bash
uvx prefect deployment inspect <flow>/onboarding -o json \
    | python3 -c 'import json,sys; [print(s["schedule"]["cron"], s["schedule"]["timezone"], "active:", s["active"]) for s in json.load(sys.stdin)["schedules"]]'
```

Check the cron and timezone match the user's intended **local** time.

## Phase 3 — Safety net (agent-driven; this is what makes Cloud valuable)

Frame: "If this breaks at 3am — or silently stops running — nobody finds out.
Two alerts fix both." Confirm the email address, then create both automations
via the raw API. Do **not** use `prefect automation create` here: email is a
Cloud-only action the open-source client schema rejects; `prefect api` sends
payloads unvalidated.

Gather the three values each payload needs, then write them **literally**
into the JSON below — no shell variables (they don't survive between your
tool calls), no placeholders left behind:

- **Deployment UUID**: `uvx prefect deployment inspect <flow>/onboarding -o json | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])'`
- **Email address**: the one the user confirmed above.
- **Staleness window** in seconds: ~1.5–2× the schedule interval, so a
  slightly late run doesn't page anyone. Daily schedule → 36h (129600).
  Never set it equal to the interval itself.

Pass each payload inline with `--data '...'`. The single quotes let the
shell hand the JSON over untouched — just keep apostrophes out of the
subject and body text. The full payload is visible in the command, so
double-check the UUID, email, and window before running it.

```bash
# 1. Email on failure
uvx prefect api POST /automations/ --data '{
  "name": "alert-on-failure",
  "trigger": {
    "type": "event", "posture": "Reactive", "threshold": 1, "within": 0,
    "expect": ["prefect.flow-run.Failed", "prefect.flow-run.Crashed"],
    "match_related": {
      "prefect.resource.role": "deployment",
      "prefect.resource.id": "prefect.deployment.<deployment-uuid>"
    }
  },
  "actions": [{
    "type": "send-email-notification",
    "subject": "Prefect: onboarding flow failed",
    "body": "Your flow run failed. See {{ flow_run|ui_url }}",
    "emails": ["<confirmed-email>"]
  }]
}'
```

```bash
# 2. Email if no completed run within the staleness window (catches paused
#    schedules, revoked GitHub access — failures that never emit "Failed")
uvx prefect api POST /automations/ --data '{
  "name": "alert-if-stale",
  "trigger": {
    "type": "event", "posture": "Proactive", "threshold": 1,
    "within": <stale-window-seconds>,
    "expect": ["prefect.flow-run.Completed"],
    "match_related": {
      "prefect.resource.role": "deployment",
      "prefect.resource.id": "prefect.deployment.<deployment-uuid>"
    }
  },
  "actions": [{
    "type": "send-email-notification",
    "subject": "Prefect: onboarding flow is overdue",
    "body": "No completed run within the expected window. Check the deployment: <deployment-url>",
    "emails": ["<confirmed-email>"]
  }]
}'
```

Verify: `uvx prefect api POST /automations/filter --data '{}' | python3 -c 'import json,sys; [print(a["name"]) for a in json.load(sys.stdin)]'`
shows both. If the API rejects the payload (Cloud action schemas can change),
check https://docs.prefect.io/llms.txt for the current automations docs, or
fall back to the UI: Automations → "+ Add Automation", same values.

Then **test alert 1 for real**. An unexpected parameter reliably breaks the
run at flow-call time:

```bash
uvx prefect-cloud run <flow>/onboarding --follow --parameter bogus_param=1
```

This raises `SignatureMismatchError` and lands the run in **Crashed** — note
it exercises the `Crashed` event path, not `Failed` (the alert listens for
both). Confirm the email arrives (have them check spam the first time), then
confirm a green run afterward. An untested alert is a false sense of
security.

## Phase 4 — Pick the next win (offer, let them choose)

1. **Deploy their real workload** with `--secret` for credentials
2. **Slack alerts** — point the failure automation at a Slack webhook block.
3. **CI/CD** — GitHub Actions running `prefect-cloud deploy` on push to `main`.

## Wrap-up

Recap in one short message: what's deployed (and where it executes), the
schedule and next run time, which alerts exist **and that they were tested**,
the dashboard link, and the one-liner to trigger a run. Silence from the
staleness alert is the system working.

## Troubleshooting

| Symptom                       | Likely cause                         | Fix                                                         |
| ----------------------------- | ------------------------------------ | ----------------------------------------------------------- |
| `deploy` can't see the repo   | GitHub app not installed on repo/org | Re-run `uvx prefect-cloud github setup`; check org approval |
| `ModuleNotFoundError` in logs | Missing dependency                   | Redeploy with `--with` / `--with-requirements`              |
| "Function not found"          | Wrong entrypoint                     | Path from repo root, `file.py:function`                     |
| Run stuck `Pending`/`Late`    | Quota or infrastructure issue        | Check the work pool in the UI                               |
| 401/403 from `prefect api`    | Profile not written by login         | User re-runs `uvx prefect-cloud login`                      |
| 422 creating automation       | Cloud action schema drift            | Check llms.txt docs; fall back to UI                        |
