Skip to Content

← All archived runs

Run: fix-ship-note-links

run.md

Run: fix-ship-note-links

  • lane: bug
  • branch: claude/fix-ship-note-links
  • pr: #802

lane/output/notes.md

Bug: fix-ship-note-links

  • observed: ship notes reach #product-update with a literal Dig deeper: <merged-PR URL>, and release.md files sit on main permanently reading merged: pending · expected: the note names the PR it is about, and the run record states something that is true.
  • cause: one defect, two faces — Ship's step 3 writes both artifacts, but its step 5 asks them for post-merge facts, and the merge is what carries and sends them. There is no window in which step 5 can act: investor-update.md is emailed by ship-note.yaml on the merge event, and by then the branch is merged and gone, so nothing in pipeline/runs/<slug>/** can be corrected without pushing to main.
  • fix: pipeline/stages/06_ship/CONTEXT.md — step 3 now fills the Dig deeper links before the merge (both are already known: run.md's pr: line and the changelog page's live URL) and says why they cannot be filled later; step 5 is narrowed to the one post-merge edit that genuinely works, the PR-body spec-link repoint via the API; the release.md template's merged: <yes — when / no> becomes merge: <authorised … | not authorised …>; and a Verify bullet forbids a run folder predicting its own merge. .github/workflows/ship-note.yaml — a Refuse to send an unfilled note step fails the send when a note's Dig deeper line still carries an angle-bracket placeholder, routing to the existing #alerts failure step instead of mailing the channel something broken.
  • changelog: not user-visible — internal delivery-pipeline contract and CI.

Reproduction

The evidence is the shipped record itself, across all 11 runs that have written a ship note:

Dig deeper placeholder — 3 of 11 shipped unfilled:

REFUSE  demo-scope-visuals      Dig deeper: <merged-PR URL> · <changelog entry URL>
REFUSE  preview-db-seed         Dig deeper: <merged-PR URL>
REFUSE  preview-db-wiring       Dig deeper: <merged-PR URL>

The other eight carry real URLs — so the fill is achievable, but only by ignoring the step that says to do it after the merge. Whether a run gets it right is currently down to whether the agent followed the instruction or worked around it.

release.md merge line — every value is wrong in a different way:

Value on main Runs Why it is wrong
merged: yes — … 6 written before the merge — a prediction, not a record
merged: pending 3 never updated, so permanently stale once the merge lands
merged: no — held 1 demo-scope-visuals, whose PR #798 did merge

demo-scope-visuals is the sharpest case: its record says the PR was held at the gate, and #798 is on main. Six runs guessed right, three left a placeholder, one is flatly contradicted by history — which is what a contract asking for facts that do not exist yet produces.

Verification

ship-note.yaml parses, and its step order is Checkout → Work out which runs shipped → Refuse to send an unfilled note → Send → Tell #alerts. The guard was run against all 11 existing notes: it refuses exactly the three known-bad ones and passes the eight with real URLs — no false positives on ·-separated URLs, em-dashes or prose.

Not fixed here, deliberately

  • The three already-sent notes and the ten release.md files on main are left as they are. They are the historical record of what actually shipped; rewriting them to say something the emails did not say would trade one inaccuracy for another. The fix stops the next one.
  • ship-note.yaml's last step renders in the Actions UI as "Tell" — its name is Tell #alerts the send broke, unquoted, so YAML truncates it at the #. Pre-existing, cosmetic, and a one-character fix, but outside this bug's cause; flagged rather than swept in.