ci: enrich cherry-pick PRs with Linear refs + future-proof sync workflow#30639
Conversation
Solves three structural gaps in the Linear release sync for this repo: 1. cherry-pick squash commits previously dropped per-PR Linear refs, so the Release CLI's HEAD-only scan stamped 0 issues per scheduled release. 2. linear-release-sync.yml had no manual trigger, blocking backfill and re-sync. 3. scheduled-pipeline releases stayed in 'Planned' / 'Started' indefinitely because no step moved them to the terminal stage. This commit: - Adds .github/scripts/enrich-cherry-pick-pr.py which rebuilds the cherry-pick PR body with a structured Linear refs block. Uses an allowlist of Vellum's three team prefixes (LUM, ATL, JARVIS) and optionally Linear's attachmentsForURL GraphQL when LINEAR_API_KEY is configured. Block uses HTML-comment delimiters so subsequent cherry-picks update it in place. - Updates cherry-pick-to-release.yml to invoke the enrichment script as a best-effort (continue-on-error) step after each successful cherry-pick. - Updates linear-release-sync.yml to add workflow_dispatch with a tag input, and a 'complete' step that moves the scheduled release to the terminal stage automatically. - Adds Linear closing-keyword guidance to PULL_REQUEST_TEMPLATE.md. Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b41375bdde
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| <!-- Link Linear issues with a closing keyword anywhere in this body so they're --> | ||
| <!-- picked up by the Linear Release CLI when this ships. Closes / Fixes / --> | ||
| <!-- Resolves all work; bare `LUM-1234` mentions do not. --> | ||
| <!-- e.g. `Closes LUM-1234`, `Part of ATL-539`. --> |
There was a problem hiding this comment.
Remove concrete Linear IDs from template comments
Because the new enrichment script scans the raw PR title/body with regex_linear_ids(...) and does not strip HTML comments, any PR opened from this template that leaves the hidden comments intact will contribute the example IDs here. When such a PR is cherry-picked, the release PR will get generated Closes LUM-1234 / Closes ATL-539 lines, so Linear release sync can attach bogus issues to every affected release; use non-matching placeholders or strip comments before scanning.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Caught at the same moment by the Linear linkback bot itself — it attached this PR to the real LUM-NNNN and ATL-NNN issues. Pushed 7f76dcc which:
- Switches the template's example identifiers from
LUM-1234/ATL-539(digits → matched by Linear's regex) toLUM-NNNN/ATL-NNN(letters → not matched). - Same swap in the enrichment script's docstring for consistency.
- Updates this PR's description so it stops cross-linking to unrelated existing issues.
Verified my Vellum-team-prefix regex is \\b(LUM|ATL|JARVIS)-(\\d{1,9})\\b — NNNN/NNN have no digits and don't match, confirming the bot's analysis was correct.
Resolved.
Real-looking IDs like LUM-1234 in the PR template's hidden comments would survive into squash commit bodies if authors don't delete the section, and would be matched by Linear's extractor even though they live inside HTML comments. Switching to LUM-NNNN / ATL-NNN placeholders keeps the example readable while ensuring no spurious cross-linking to real issues. Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>
| # a workspace-scoped Linear API key, distinct from the pipeline | ||
| # access key. | ||
| LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }} | ||
| run: python3 .github/scripts/enrich-cherry-pick-pr.py |
There was a problem hiding this comment.
🔴 Enrichment script unreachable: working tree is on cherry-pick branch, not main
After the "Cherry-pick and create PR" step, the working tree is on the cherry-pick branch (via git checkout "$CHERRY_PICK_BRANCH" at .github/workflows/cherry-pick-to-release.yml:74 or :76-77). The cherry-pick branch is based on the release branch, which was created before this PR merged to main. Therefore .github/scripts/enrich-cherry-pick-pr.py does not exist in the working tree when the enrichment step tries to run it. The command python3 .github/scripts/enrich-cherry-pick-pr.py will fail with FileNotFoundError on every invocation until a release branch is cut from a main commit that includes this script. continue-on-error: true prevents this from blocking the cherry-pick, but the entire enrichment feature is non-functional for all existing release branches.
Suggested fix approach
Before running the script, restore it from the original checkout ref (main). For example:
run: |
git show origin/main:.github/scripts/enrich-cherry-pick-pr.py > /tmp/enrich-cherry-pick-pr.py
python3 /tmp/enrich-cherry-pick-pr.py| run: python3 .github/scripts/enrich-cherry-pick-pr.py | |
| run: | | |
| git show origin/main:.github/scripts/enrich-cherry-pick-pr.py > /tmp/enrich-cherry-pick-pr.py | |
| python3 /tmp/enrich-cherry-pick-pr.py |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Real bug, confirmed by walking the workflow's git state:
- The initial
actions/checkout@v6.0.2(line 28) puts the working tree onmainwith full history (fetch-depth: 0). - The "Cherry-pick and create PR" step (line 76 or line 73-74)
git checkouts the cherry-pick branch, which is based onrelease/vX.Y.Z. - That release branch was cut before this PR exists, so it does not have
.github/scripts/enrich-cherry-pick-pr.py. - By the time the enrichment step runs, the script file is genuinely missing from the working tree.
Result without a fix: python3 .github/scripts/enrich-cherry-pick-pr.py → No such file or directory on every cherry-pick. continue-on-error: true hides the failure but the whole enrichment is non-functional.
Applied the suggested fix in 1a8764d with a slight tweak — explicit git fetch origin main --quiet before git show, so the script is always loaded from the freshest origin/main (covers the case where a long-lived runner has stale refs). Script writes to /tmp and runs from there; its internal git operations (git log, git rev-parse, etc.) read from .git, which is the same regardless of working-tree ref, so behavior is unchanged.
Resolved.
The previous step leaves the working tree on the cherry-pick branch, which is based on the release branch — a branch that pre-dates this script. Reading the script from origin/main into /tmp makes it available regardless of which release branch is being cherry-picked to. The script itself only reads git refs (git log, git rev-parse, etc.), so running from /tmp doesn't change behavior. Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>
There was a problem hiding this comment.
Vex review — APPROVED ✅
CI-only: no application code paths. Reviewed all 3 commits at HEAD 1a8764d.
What this fixes: Three structural gaps that made the Linear Release CLI stamp 0 issues on vellum-assistant releases shipped via cherry-pick PR squashes — (1) squash body drops original PR bodies, (2) release branches aren't ancestors of each other (Linear's CLI falls back to HEAD-only scan), (3) Linear requires closing-keyword magic words, not bare mentions.
The two bugs Devin caught and self-fixed:
-
Template example IDs were real-looking (
LUM-1234/ATL-539→LUM-NNNN/ATL-NNN). Commit7f76dccconfirmed correct — regex is\b(LUM|ATL|JARVIS)-(\d{1,9})\b;NNNNhas no digits, doesn't match. Linear's linkback bot actually triggered on the original values before the fix landed, confirming the risk was real. -
Enrichment script unreachable on existing release branches. After the cherry-pick step, working tree is on the cherry-pick branch (based on
release/vX.Y.Z, cut before this PR merged), so.github/scripts/enrich-cherry-pick-pr.pydoesn't exist in the tree. Commit1a8764dfixes this correctly:git fetch origin main --quiet && git show origin/main:.github/scripts/... > /tmp/...— always loads the script fromorigin/mainregardless of working tree state. Git operations inside the script (git log,git rev-parse) read from.gitwhich is unaffected. ✅
Rest of the review:
continue-on-error: trueon enrichment step — enrichment failure cannot block cherry-picks ✅- Action SHA pinned at both
syncandcompletesteps (755d50b5), samecli_version: v0.10.0✅ workflow_dispatchis additive — existingrelease: publishedbehavior unchanged ✅- Pre-release filter correctly gates on
event_name == 'release'so manual dispatch bypasses it ✅ - Python script is idempotent (HTML comment delimiters replace in-place), handles Linear API errors gracefully, regex restricted to known team prefixes (LUM/ATL/JARVIS) to prevent false positives ✅
LINEAR_API_KEYenrichment path is optional — degrades to regex-only if secret not set ✅
Anti-patterns KB: No SwiftUI, React, or application patterns apply — CI scripting only.
Vellum Constitution — Article VI (Accountability): This PR directly strengthens auditability by ensuring every shipped issue shows up correctly on its Linear release. When an issue is stamped "released in v0.8.2" it now actually means it shipped in that build. That's exactly the kind of audit trail Article VI asks for.
No blocking findings. Both Devin-flagged bugs are confirmed fixed at HEAD.
Team workflow links Linear issues via bare mentions (Dev-panel linkage + Linear's auto-close-on-merge setting), so the closing-keyword prescription is redundant for ordinary linkage and won't be adopted. The Release CLI's magic-word requirement is handled separately by the cherry-pick enrichment script in this PR, which queries Linear's attachmentsForURL GraphQL to discover Dev-panel-linked issues and writes the closing-keyword form into the cherry-pick PR body before squash-merge. No engineer behavior change needed. Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>
|
Thanks @vex-assistant-bot. One revision since your approval: dropped the PR template closing-keyword hint in 5698b8a after the team clarified that bare The Release CLI's separate magic-word requirement is still solved — by the enrichment script's |
…s) (#30642) The enrichment script added in #30639 was meant to inject magic-word Linear identifiers into cherry-pick PR bodies so the Linear Release CLI could extract them from the squash commit. Empirical testing against Linear's GraphQL API proved this is unnecessary: when the CLI passes `pullRequestReferences` to `releaseSyncByAccessKey`, Linear's server resolves each PR to its Development- panel-linked issues and stamps the release with them, regardless of whether any Linear identifier appears in commit text. The cherry-pick squash commit already contains `(#NNN)` references to each original main PR, which the CLI extracts as `pullRequestReferences`. Linear's server then finds each main PR's linked issues via the Development panel and attaches them to the release automatically. Test case: PR vellum-ai/vellum-assistant-platform#6655 has Dev-panel attachment ATL-545 and zero LUM/ATL/JARVIS-N mentions in its title, branch, body, or commit message. Calling `releaseSyncByAccessKey` with only `pullRequestReferences: [#6655]` (no `issueReferences`) produced a release stamped with ATL-545. Repeating the test with PR #6708 (LUM-1536 linked) produced a release stamped with LUM-1536. The resolution is purely server- side via attachment records. Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Prompt / plan
Linear Releases sync (PR #30622, merged) is dormant for this repo because of three structural gaps in how Linear's Release CLI consumes commit history when the source repo ships via cherry-pick PRs. This PR closes all three, plus adds future-proofing.
The three gaps
Cherry-pick squash commits drop Linear refs.
cherry-pick-to-release.ymlaccumulates multiple cherry-picks into one PR per release branch. When that PR squash-merges, the squash body concatenates only the titles of the cherry-picked commits — not their bodies. AnyCloses LUM-XXXin the original main PRs is lost.Release branches aren't ancestors of each other.
git merge-base --is-ancestor v0.8.0 v0.8.1returnsfalse. The Linear CLI usesmerge-base --is-ancestorto filter candidate base SHAs; when none qualifies it falls back to inspecting only HEAD. Combined with (1), scheduled releases would stamp 0 issues even after backfill.Magic-word requirement. Linear's extractor only catches
LUM-NNNNwhen preceded byCloses/Fixes/Resolves/Part of/Refs. Bare mentions are intentionally ignored.What this changes
.github/scripts/enrich-cherry-pick-pr.pywalksorigin/release/vX.Y.Z..HEAD, fetches each cherry-picked main PR viagh pr view, and rebuilds a structured Linear-refs block in the cherry-pick PR's body. When the cherry-pick PR squash-merges, theCloses LUM-XXXlines land in the squash commit body — exactly what Linear's HEAD-only fallback scans for.Discovery order:
attachmentsForURLGraphQL (docs) — optional, runs only whenLINEAR_API_KEYis configured. Catches PRs linked via Linear's Development panel even with no identifier in the body.LUM,ATL,JARVIS). One-line constant; adding a fourth team is trivial.Block uses HTML-comment delimiters so subsequent cherry-picks update in place.
.github/workflows/cherry-pick-to-release.ymlexports the cherry-pick PR number and adds acontinue-on-error: truestep invoking the script. Enrichment cannot fail the cherry-pick..github/workflows/linear-release-sync.yml:workflow_dispatchwith ataginput for backfill / re-sync. Pre-release filter is gated onevent_name == 'release'.command: complete(same pinned SHA) that moves scheduled releases into the terminal stage automatically.What this no longer changes (revised after team feedback)
An earlier revision added a comment block to
.github/PULL_REQUEST_TEMPLATE.mdencouraging Linear closing keywords. Removed in 5698b8a.Team workflow already gets full Linear-issue linkage via bare mentions (Linear's GitHub integration links issues via the Development panel from any
LUM-NNNNreference, and the workspace's auto-close-on-merge setting closes them when the PR merges). The closing-keyword hint was redundant for that purpose and wouldn't be adopted organically.The Linear Release CLI's separate magic-word requirement is the real problem this PR solves — but via the enrichment script (which queries
attachmentsForURLto discover Dev-panel-linked issues and writes the closing-keyword form into the cherry-pick PR body before squash-merge) — not by pushing a PR-template convention onto engineers.Alternatives considered and rejected
linear/linear-release-actionwith a custom GraphQL caller — re-implements logic the upstream CLI already handles.--from-shaflag — slow, uncertain, doesn't fix the squash-strips-refs problem.Option B (this PR) preserves the existing release workflow entirely, requires no infrastructure changes, and is reversible by deleting the enrichment step.
Why it's safe
continue-on-error: true). Failures cannot fail the cherry-pick or any downstream job.gh pr edit; never pushes commits, never modifies workflow state, never touches other PRs.completestep uses the same pinned action SHA + CLI version assync.workflow_dispatchis purely additive.References
attachmentsForURL: https://linear.app/developers/attachmentsworkflow_dispatch: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#workflow_dispatchTest plan
LUM-,ATL-,JARVIS-are extracted andHTTP-200,AWS-1, lowercase, and future-team prefixes are rejected.yaml.safe_loadconfirms both workflows parse.v0.6.0→v0.8.1is available viaworkflow_dispatch.CLI verb checklist
N/A — no IPC routes touched.
Link to Devin session: https://app.devin.ai/sessions/8a3ce390e6e24deba0fac01d1b4a001e
Requested by: @ashleeradka