Deploy monitoring
Tell partyline when a deploy finishes and it becomes two things: a number on your dashboard, and — if you want it — an agent that investigates the failures.
It works with any provider, because it asks nothing of them beyond the one thing they all do: send a request when a deploy ends. If yours can't, curl can.
Setting this up with your own AI? Hand it this page. Every step here is scriptable — including creating the trigger — so an agent with your shell can do the whole thing. Each step has a way to check it worked.
How it works
- You create a trigger in partyline. That's the address deploys report to, and the settings for what happens next.
- Your deploy tells it what happened —
succeededorfailed. - partyline records every outcome, and starts an agent only on the outcomes you chose.
Nothing is installed on your provider, and partyline never connects out to it. Everything arrives as one HTTP request that you control.
Step 1 — Make the trigger
From your shell (an agent can do this):
$ ptln trigger targets # which machines, and the projects each advertises
ptln trigger create "Deploy — production" \
--slug deploy-prod --project my-app --on failed \
--task @investigator.md --key-only | gh secret set PARTYLINE_DEPLOY_KEY--key-only prints only the key on stdout, so it pipes straight into whatever stores it and never passes through a transcript or a clipboard. Everything else goes to stderr, so you still see what was created.
Leave --gate at its default (review) while you're testing — failures land in the backlog instead of starting work immediately.
Or in the web app: Settings → Integrations → Triggers → Add a trigger, filling in name, address (deploy-prod), the project and machine an investigation should run on, what to ask, and when.
Either way you get an address and a key, shown once. Keep both. Check it exists with ptln trigger ls.
For "what to ask", something like:
A deploy of {{env}} failed.
Commit: {{ref}}
Where: {{url}}
Find the cause and explain it. Only propose a code change if you are
confident it is the fix.
--- build output ---
{{log}}You'll get an address and a key, shown once. Keep both.
Step 2 — Report deploys to it (usually scriptable)
The request is the same everywhere:
curl -X POST https://partyline.sh/api/v1/t/deploy-prod \
-H "authorization: Bearer plt_…" \
-H "content-type: application/json" \
-d '{
"outcome": "failed",
"env": "production",
"ref": "a1b2c3d",
"url": "https://…",
"provider": "github",
"log": "…last 200 lines…"
}'Only outcome is required. Everything else makes the metric and the investigation better:
ref— your commit sha or build id. Also prevents duplicates: the samereftwice will never start two investigations, which matters because redelivery is normal.log— the tail of your build output. This is what an agent actually needs; without it, it can only guess. Your CI already has it, so sending it costs nothing and means partyline never needs access to your provider.env,provider,url,duration_ms— for the dashboard.
GitHub Actions
Add two steps at the end of your deploy job. if: failure() and if: success() do the deciding, so nothing has to parse anything:
- name: Tell partyline the deploy failed
if: failure()
run: |
curl -sS -X POST https://partyline.sh/api/v1/t/deploy-prod \
-H "authorization: Bearer ${{ secrets.PARTYLINE_KEY }}" \
-H "content-type: application/json" \
-d "$(jq -n \
--arg ref "${{ github.sha }}" \
--arg url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
'{outcome:"failed", env:"production", provider:"github", ref:$ref, url:$url}')"
- name: Tell partyline the deploy succeeded
if: success()
run: |
curl -sS -X POST https://partyline.sh/api/v1/t/deploy-prod \
-H "authorization: Bearer ${{ secrets.PARTYLINE_KEY }}" \
-H "content-type: application/json" \
-d '{"outcome":"succeeded","env":"production","provider":"github"}'Put the key in Settings → Secrets and variables → Actions as PARTYLINE_KEY.
To send the log too, capture it first — your-deploy-command 2>&1 | tee /tmp/deploy.log — then add --arg log "$(tail -c 8000 /tmp/deploy.log)" and log:$log to the jq call.
Anything else
The same curl works from a Makefile, a deploy script, a cron job, or a container's entrypoint. If you can run one command after a deploy, you're done — you do not need a provider integration and there is nothing to install.
For providers that fire one webhook for every result (a single "deployment finished" hook, whatever the result), send it straight to the trigger and let partyline read the status out of the payload. On the trigger, set:
- Status field — where the result lives in their payload, e.g.
workflow_run.conclusion - Means success — the value that means good, e.g.
success
Anything that isn't that value counts as a failure. That's how a provider whose words we've never seen still produces a usable number.
Your provider
Two patterns cover everything. Which one you use depends on what your provider will let you configure — not on whether we support it.
A — you send the request. Anything that runs commands: GitHub Actions, GitLab CI, CircleCI, Jenkins, a Makefile, flyctl deploy in a script, a cron. You add a step, you decide the outcome, nothing has to be parsed. Most reliable, and the one to prefer.
B — the provider sends it. A hosting platform that posts a webhook when a deploy finishes: Vercel, Render, Netlify. Either you point separate events at separate triggers, or you point everything at one trigger and tell it where the status lives.
GitHub Actions
Pattern A. Covered in full above — two steps, if: failure() and if: success().
GitLab CI
Pattern A. Same idea, using GitLab's own job rules:
report:
stage: .post
image: alpine:latest
variables: { GIT_STRATEGY: none }
before_script: [apk add --no-cache curl]
script:
- |
OUTCOME=$([ "$CI_JOB_STATUS" = "success" ] && echo succeeded || echo failed)
curl -sS -X POST https://partyline.sh/api/v1/t/deploy-prod \
-H "authorization: Bearer $PARTYLINE_KEY" \
-H 'content-type: application/json' \
-d "{\"outcome\":\"$OUTCOME\",\"env\":\"production\",\"provider\":\"gitlab\",\"ref\":\"$CI_COMMIT_SHA\",\"url\":\"$CI_PIPELINE_URL\"}"
when: alwaysAdd PARTYLINE_KEY under Settings → CI/CD → Variables, masked.
CircleCI
Pattern A. Add a step with when: always at the end of your deploy job and branch on the result, exactly as above. CIRCLE_SHA1 is the commit and CIRCLE_BUILD_URL is the link.
Fly.io, Railway, Heroku, or a deploy script
Pattern A. These are usually deployed by a command — flyctl deploy, railway up, git push heroku — so wrap it:
if ./deploy.sh 2>&1 | tee /tmp/deploy.log; then OUTCOME=succeeded; else OUTCOME=failed; fi
curl -sS -X POST https://partyline.sh/api/v1/t/deploy-prod -H "authorization: Bearer $PARTYLINE_KEY" -H 'content-type: application/json' -d "$(jq -n --arg o "$OUTCOME" --arg l "$(tail -c 8000 /tmp/deploy.log)" '{outcome:$o, env:"production", ref:"'"$(git rev-parse HEAD)"'", log:$l}')"This one also sends the log, which is what makes an investigation useful.
Vercel
Pattern B, using separate events. Vercel lets you choose which events a webhook receives, so let it do the deciding and nothing needs parsing.
Make two triggers — say deploy-prod-ok and deploy-prod-fail — then add two webhooks under Project Settings → Webhooks:
| Webhook URL | Subscribe to |
|---|---|
https://partyline.sh/api/v1/t/deploy-prod-ok | deployment.succeeded |
https://partyline.sh/api/v1/t/deploy-prod-fail | deployment.error, deployment.canceled |
On the success trigger set the outcome to succeeded; on the failure one, failed. Set them under Status field → leave empty, and instead put the fixed value in the trigger's own outcome setting.
Vercel's payload puts the environment at payload.target and the deployment link at payload.links.deployment, so those are the paths to use if you want them in the task.
Vercel signs webhooks with a secret and does not send an
authorizationheader. Point the webhook at a trigger whose key is in the URL path only if your plan supports custom headers; otherwise use pattern A from a GitHub Action, which is why we recommend it.
Render
Pattern B, using one webhook and a status field — Render sends a single deploy_ended event carrying the result.
Create the webhook under Settings → Webhooks, pointed at your trigger. Then on the trigger set:
| Setting | Value |
|---|---|
| Status field | data.status |
| Means success | succeeded |
Render's data.status is succeeded, failed or canceled — so anything that isn't succeeded is treated as a failure, which is what you want.
Netlify
Pattern B, using separate events. Under Site configuration → Notifications → Deploy notifications, add two outgoing webhooks:
| Event | URL |
|---|---|
| Deploy succeeded | https://partyline.sh/api/v1/t/deploy-prod-ok |
| Deploy failed | https://partyline.sh/api/v1/t/deploy-prod-fail |
As with Vercel, each trigger carries a fixed outcome, so nothing needs to be parsed.
Anything not listed
Use pattern A. If you can run one command after your deploy, you're done — there is nothing to install and no integration to wait for. If your provider only offers webhooks and sends one event for every result, use pattern B with a status field; if it lets you pick events, make two triggers.
Step 3 — Check it worked
Fire it by hand:
$ curl -X POST https://partyline.sh/api/v1/t/deploy-prod -H "authorization: Bearer plt_…" -H "content-type: application/json" -d '{"outcome":"succeeded","env":"production","ref":"test-1"}'You should get:
{"ok":true,"recorded":true,"outcome":"succeeded","acted":false}"outcome":"succeeded"— it understood you. If this saysunknown, the value didn't match anything recognisable; sendsucceededorfailedexactly."acted":false— nothing was started, which is right for a green deploy once you've set investigate on failures only.
Then repeat with "outcome":"failed" and "ref":"test-2". You should see "acted":true and a run id.
Deploys appears on your dashboard once the first one is recorded.
If it doesn't work
| What you see | What it means |
|---|---|
401 | The key is wrong, missing, or revoked. It needs the Accept inbound work permission. |
404 | The address doesn't match a trigger on your team, or the trigger is switched off. |
{"deduplicated":true} | That ref was already reported. Use a new one to test again. |
"outcome":"unknown" | Nothing in the request said what happened. Send outcome, or set the status field on the trigger. |
Step 4 — Investigate failures
Once the green path is recording, change the trigger to investigate on failures only. Now a failed deploy starts an agent and a successful one doesn't.
When one starts you get a Slack message and an email saying an agent is investigating, with a link — because nobody asked for this run, so you should hear about it at the moment it begins rather than after it has done something. It shows up on the Build board like any other work.
The agent explains, and proposes. It won't merge anything: the investigation lands as a report, and a code change only if it's confident one is the answer. A failed deploy is often infrastructure rather than code, and an agent that opens a PR for every red build is one you'll learn to ignore.
What partyline can and can't see
partyline holds no credentials for your provider and never connects to it. That's deliberate, and it has one consequence worth knowing: an agent can only read the build output you send it. That's why log matters — your CI already has it.
If you'd rather not send logs, an agent running on your own machine can fetch them with a CLI that's already signed in there (gh run view --log-failed). That works, but only from a machine that can reach your provider.
One trigger, in detail
/triggers/<id> shows a single trigger: what it listens for, the persona it wakes, and what it has
actually done — its recent fires with their outcomes. It is the page to open when a trigger looks
like it fired and nothing happened.