ship-note-run-detectionrun.mdlane/output/notes.mdship-note.yaml now resolves the shipped run from the PR's file list instead of a
local git diff against a commit the squash-merge destroyed, and goes red instead of green when
it cannot resolve or cannot send. The note's content, the 60-word cap and the merge-gated
authorisation model are untouched..github/workflows/ship-note.yaml: three fixes and one record correction (below).git diff --name-only "$BASE_SHA" "$HEAD_SHA" cannot work on a merged PR. After a squash-merge the
PR head commit is not reachable from main, and once the branch is deleted the object is gone, so
fetch-depth: 0 never helped. On PR #798 it printed fatal: bad object a497cc98…, || true
swallowed it, slugs came out empty and the job reported success having sent nothing.
Not specific to #798 — it has never worked. All six runs of this workflow since it landed on
2026-08-10 show Send: skipped, including the very first
(run 31384388833, the
metric-dictionary-and-claim-surface merge). No ship note has ever been delivered by it; the Resend
account shows no ship-note mail at all.
Now: gh api repos/{repo}/pulls/{n}/files. Chosen over diffing
merge_commit_sha against its first parent because the PR file list is correct for every merge
strategy — a rebase-merge sets merge_commit_sha to the last rebased commit, so a first-parent
diff would silently miss a ship note introduced in any earlier commit. That is the same class of
silent miss being fixed here. It also needs no git history at all, so fetch-depth: 0 (a full clone
of every branch in the repo) is gone; the checkout now pins merge_commit_sha, the exact tree the
merge produced. Verified against #798's real file list: resolves demo-scope-visuals.
The path pattern matches the current 05_ship/ layout plus every layout still on disk — 06_ship/
from the six-stage runs, and the pre-expansion 03_release/ and 03_ship/ — the same fallbacks
send-investor-update.sh already resolves. (Rebased onto the pipeline/ → .icm/ restructure of
#803, which renumbered Ship from 06 to 05 and moved this run record; the pattern is anchored at
.icm/runs/.)
|| true is gone and the step runs under set -euo pipefail. An unresolvable file list is
now ::error:: + exit 1 rather than a green "nothing to send".sed -n …p, not grep, so a genuine no-match (exit 0) stays
distinguishable from a failure — the distinction the || true erased.::warning:: naming
every investor-update.md path the PR did touch, so the log explains itself. This case only
arises if a PR deletes or renames a note.RESEND_API_KEY, INVESTOR_RECIPIENTS and
EMAIL_FROM, and names each missing one. Previously only RESEND_API_KEY was checked in the
workflow; the rest surfaced as a one-line die from inside the script.INVESTOR_EMAIL_FROM is gone. It existed only as an optional override of EMAIL_FROM for
ship-note mail, and nothing anywhere set or read it — the whole monorepo had exactly two mentions,
both inside the fallback expression itself. EMAIL_FROM is the load-bearing name: the platform's
own mail reads process.env.EMAIL_FROM (packages/services/src/email/send-html-email.ts) and it is
catalogued in turbo.json's globalEnv. Removed from the script's from resolution, its config
docs, the workflow's env: block and the guard; turbo.json's catalogue note is corrected to say
the ship note has no sender of its own. One address, one variable to set, one thing to diagnose.
Unchanged: the github.event.pull_request.merged == true guard, so a closed-without-merge PR still
sends nothing. permissions: gains pull-requests: read for the API call.
The failing run's step env printed all four blank. GitHub renders a set secret as ***, so blank
means absent: RESEND_API_KEY, INVESTOR_RECIPIENTS, EMAIL_FROM and INVESTOR_EMAIL_FROM do not
exist at repo level. No values are invented here. After the convergence above there are three to
set, not four — RESEND_API_KEY, INVESTOR_RECIPIENTS, EMAIL_FROM — under Settings → Secrets
and variables → Actions. Until they exist the job now fails loudly and posts to #alerts instead
of passing quietly.
demo-scope-visuals ship recordBoth files were already on main and stale:
investor-update.md — Dig deeper links filled: PR #798 and
https://help.sustentus.com/changelog/2026-08-13-demo-scope-visuals. Body untouched (56 words,
and the links line sits outside the cap by contract).release.md — records the squash-merge (dd84b1e, 2026-08-13) and records the send honestly: it
did not go out, with both reasons and the manual re-send instructions.The help-centre host is help.sustentus.com — the custom domain on the Vercel help-centre
project, confirmed serving the entry with a 200. Note for whoever reads the older notes: the three
most recent runs used the help.sustentus.com alias, which sits behind Vercel
deployment protection and 302s to SSO. Those links are not publicly openable. Not corrected here —
out of this chore's scope, but worth a follow-up.