PullPulse
docs · releases · 4 min

Releases and what shipped

When a repository publishes a release, PullPulse posts one card to the channels that asked for it and edits that card as the release deploys.

When a repository publishes a release, PullPulse posts one card to the channels that asked for it and edits that card as the release deploys.

Release news is the one surface off until asked for. A channel that watches a repository's pull requests has not asked to hear about its tags, so releases is false by default — turn it on per route in .pullpulse.yml, or tick Announce releases on the watch form. See the configuration reference.

The release card

The card posts on GitHub's release.published with draft: false — the one event that fires for stable releases and pre-releases alike. A later edited updates the same message; unpublished and deleted fold it.

┌ card ────────────────────────────────────────────┐
│ ⬆  v2.14.0 · acme/billing
│    ● Tagged · ● Notes · ◔ Deploy
│    🚢 12 pull requests from 5 people · since v2.13.2 · 6 days
│    _Idempotent webhooks, proration fix, signing-key rotation._
│    [Release notes] [Diff v2.13.2…v2.14.0] [Draft announcement]
└──────────────────────────────────────────────────┘
⬆ Deploying to production · by @dana · 4:02 PM
tagged by @dana · a3f2c1d · pre-release · 🔒 immutable · 3 Features · 7 Bug fixes
▸ What's in it · 12 pull requests · 5 people
                                          🔍 Explain 🔕 Mute releases

The counts lead because they are the only part a reader can act on without opening anything: how big the release is, and what it is a release from. A release whose notes named nothing says 🚢 no pull requests we could resolve rather than 0 pull requests, which would read as a claim that nothing shipped. One line of the body is quoted under it; the body itself, up to 125 000 characters of Markdown, is never rendered whole.

The three-stop rail

Same glyph family as the pull request card's four-stop rail, three stops instead of four.

Stop reads
Tagged always — the card exists because a release was published
Notes when the generated notes named something, when they named nothing
Deploy nothing reported · queued or pending · in progress · succeeded · failed

Badges and the meta line

pre-release and 🔒 immutable sit on the meta line, not in the title: they are facts about the release rather than states of it — the rail says how it is doing — and a badge in the title would push the tag off the end. The line reads tagged by @dana · a3f2c1d, then the badges, then the count per category the notes used.

What's in it, and where it comes from

One collapsible container, collapsed by default: a channel that opted into release news did not opt into twelve lines of it every time, so the subtitle carries the counts and the click carries the list. The grouping is GitHub's own. PullPulse asks POST /releases/generate-notes with previous_tag_name; GitHub resolves the pull requests, applies the repository's .github/release.yml categories, and persists nothing. The returned Markdown is parsed for the headings and the pull request numbers, and each number is joined to PullPulse's own pull_requests rows for the Slack mention and the stored brief — which is why the container names people rather than logins. A category with no heading is listed as Changes. When generate-notes fails, the fallback walks compare base…head and commits/{sha}/pulls, capped at 100 commits.

Draft announcement is the AI action: a customer-facing paragraph written from the briefs of the included pull requests.

The deploy stop

Deployments come from deployment_status, tied to the release by deployment.sha matching the tag's commit or deployment.ref equalling the tag name — GitHub keeps no explicit link between the two. The ledger line above the meta line states the latest thing only: Deploying to production, Deployed to production, Deploy failed, each linked to its run and stamped in the reader's own clock. Since a job can use an environment with deployment: false, a repository that reports no deployment events falls back to workflow_run on the tag, and the stop reads when neither exists. A release with no deployment gets no ledger line rather than a line saying nothing happened.

Shipped today

Above three releases from one repository in one UTC day the cards stop, and one Shipped today message per repository per day is edited in place instead — a repository that ships continuously would otherwise hand a channel a card an hour. It is a header, a strip, and a table with one row per release: Release, Rail, What, PRs, By, At, newest first and never ranked by anything else, because the reader's question is "what has shipped since I last looked".

Back on the pull request

When a release ships a merged pull request, that pull request's fold gains · shipped in v2.14.0, linked to the release; its Work Object pane gains a Release field; and the digest's merged bucket says the same.

Weekly notes

Everything the cards read is stored, which is what makes "what shipped this week" answerable without asking GitHub again: the week's releases with their items, summarised from the stored briefs and grouped by release, alongside who worked on what. It is a per-channel permission in .pullpulse.yml like pulseReport, not a schedule — see the configuration reference.

What GitHub has to allow

Releases need three webhook subscriptions and two read-only permissions that older installations do not have: the events release, deployment_status and workflow_run, and the permissions deployments and actions. The permissions are what GitHub gates the events' delivery on, and it does not warn — without them the events are silently not sent, so the symptom is a deploy stop that never moves rather than an error anywhere. Adding one re-prompts every installation's org admin, and until an org accepts, its cards render tagged and noted with the deploy stop pending forever. Operators: see operations.