ENG-2099 Test and fix Obsidian Linear release pipeline - #1311
ENG-2099 Test and fix Obsidian Linear release pipeline#1311trangdoan982 wants to merge 12 commits into
Conversation
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
isExternalRelease() returned true for beta as well as stable, and that one boolean gates both the GitHub prerelease flag and the push to the publish repo's main branch. A beta therefore shipped as a full release and overwrote manifest.json on discourse-graph-obsidian's main branch, which is what the Obsidian community store reads to pick a version (ENG-2106). Only a bare x.y.z is external now, so betas and alphas stay pre-releases and leave the publish repo's main branch alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The workflows referenced secrets.OBSIDIAN_LINEAR_RELEASE_KEY, which does not exist. The repo secret is named LINEAR_RELEASES_ACCESS_KEY_OBSIDIAN, so the input resolved to an empty string and every run died at the Linear sync step with "access_key input is required". The name was correct on eng-1767 (which is why it passed there on 2026-05-24) and was changed on merge. obsidian-main.yaml has never once succeeded on main since: 6 failures, 3 cancelled, 0 successes. Note the sync step runs last, after publish has already written to the publish repo, so these red runs were still mutating production. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3635119 to
7b33e2d
Compare
Do not merge while this is in place. Retargets publish.ts at the private discourse-graph-obsidian-test repo so the full pipeline (GitHub release, manifest push, Linear sync) can be exercised from this branch via workflow_dispatch without touching the real publish repo. Running from a branch isolates which code runs, not where it writes, so the target constant is what actually sandboxes the test. Revert before merge — see TODO(ENG-2099) in scripts/publish.ts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Do not merge while this is in place. publish.ts reads its credential from the OBSIDIAN_PLUGIN_REPO_TOKEN env var, and the workflow is what maps a secret onto it, so repointing the env var is enough to keep a test run off the real publish repo without touching the script. Pairs with the temporary test-repo target in scripts/publish.ts. Requires a repo secret named OBSIDIAN_TEST_REPO_TOKEN, scoped to discourse-graph-obsidian-test with Contents: read and write. Revert before merge — see TODO(ENG-2099) in the workflow env block. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gh release list had no --repo, so it queried the monorepo, which has zero releases and zero tags. LATEST_N was therefore always empty and the else branch always won, recomputing -beta.1 forever. Confirmed in run 32530653062: "next beta: 1.5.2-beta.1" followed by "already exists, skipping creation" and then "Publication completed successfully" — a green run that shipped nothing. Point the lookup at the publish repo, where releases actually live. The lookup also needs a credential that can read the target, since github.token cannot see a private repo; reuse whatever publish.ts is configured with so this works for both the real public repo and the private test target. Also drops the sed that stripped a -beta suffix from package.json. After the Bug A fix updateLocalVersion only runs for stable releases, so package.json only ever holds a bare x.y.z. Verified locally against both repos: latest_n=1 -> next=1.5.2-beta.2. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Do not merge while this is in place. Same treatment as obsidian-main.yaml so the stable release path can be exercised against the throwaway test repo. Revert before merge — see TODO(ENG-2099) in the workflow env block. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
shouldExclude tested patterns with relativePath.includes(), and turned glob patterns into unanchored regexes, so any file whose name merely contained an excluded word was silently dropped from the publish repo. "out" ate src/components/DatacoreCallout.tsx, and "*.log" becoming /.*.log/ ate src/utils/nativeJsonFileDialogs.ts via "diaLOGs". Confirmed on a real publish: 101 local src files, 99 published. Match whole path segments instead, and anchor globs to a single segment with regex metacharacters escaped. Verified against the full apps/obsidian tree: exactly those two files flip from excluded to included and nothing flips the other way. .env, .env.branch, .env.example, nested .env.local, *.pem, *.log, node_modules, dist, scripts and .git remain excluded; Logger.ts, Layout.tsx, distance.ts, environment.ts and buildIndex.ts are correctly kept. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
updateManifest only rewrote manifest.json in the staging directory. package.json was copied at its pre-bump value, and updateLocalVersion — which bumps it — runs after updateMainBranch has already pushed. The published package.json was therefore always one release behind, permanently. Observed on both repos: the test repo showed manifest.json 9.9.9 alongside package.json 1.5.2 right after publishing 9.9.9, and the production repo shows manifest.json 1.5.2-beta.1 alongside package.json 1.5.2. Cosmetic for users, since Obsidian reads manifest.json, but the published repo was internally inconsistent and anything reading package.json got the wrong answer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…kflow The Commit version bump step can never succeed on main. The "Main Branch Protection" ruleset requires a pull request on the default branch and does not list github-actions[bot] as a bypass actor, so git push is rejected. Because the step sits after publishing, a real stable release would create the GitHub release and push the publish repo, then fail here and never reach the Linear sync/complete steps. Retrying does not help: updateMainBranch reports no changes, createGithubRelease skips the existing release, updateLocalVersion rewrites the same files, and the push is rejected again. The Linear half of the pipeline was unreachable for that version. The step's own guard gives us the way out. Bumping package.json and manifest.json by hand in the release PR means publish.ts writes values that already match, git diff --quiet passes, and nothing needs pushing from CI — so the remaining steps run. Commented out rather than deleted so the intent and the re-enable conditions stay visible. This also explains why the workflow's two green runs on 2026-05-24 proved nothing: they ran on a branch, where git push targets the branch, not main. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comments that narrated what this PR fixes belong on the PR, not in the source. Removed the bug references and the specific file names that were dropped, and kept only what a future reader needs without this PR for context: - the community store reading manifest.json from the publish repo's main branch, which is why only finished releases are external - the segment-matching contract for exclude patterns - the ordering constraint that makes updateStagedPackageVersion necessary - releases existing only in the publish repo, which is why the lookup needs an explicit --repo - why the version-bump step is disabled and how to re-enable it TEMP revert markers reduced to a single line each. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
| @@ -215,23 +216,14 @@ const validateVersion = (version: string): void => { | |||
| }; | |||
|
|
|||
| const isExternalRelease = (version: string): boolean => { | |||
There was a problem hiding this comment.
Bug A: -beta versions were published as official releases
This predicate returned true for beta as well as stable, and one boolean gated two separate decisions: the GitHub prerelease flag, and whether to push manifest.json to the publish repo's main branch. So a beta shipped as a full release, and it overwrote the manifest the Obsidian community store reads to pick a version. That is ENG-2106.
The publish repo's main still sits at 1.5.2-beta.1 today, with five Release v1.5.2-beta.1 commits, two of them landing after stable 1.5.3 shipped on 7 Aug.
Verified across fourteen version inputs, then in five beta runs: every release came out prerelease=true, and the test repo's main kept exactly one commit and one file throughout.
| uses: linear/linear-release-action@v0 | ||
| with: | ||
| access_key: ${{ secrets.OBSIDIAN_LINEAR_RELEASE_KEY }} | ||
| access_key: ${{ secrets.LINEAR_RELEASES_ACCESS_KEY_OBSIDIAN }} |
There was a problem hiding this comment.
Bug B: every run on main failed at the Linear step
This referenced secrets.OBSIDIAN_LINEAR_RELEASE_KEY, which does not exist. The real secret is LINEAR_RELEASES_ACCESS_KEY_OBSIDIAN, so the input resolved to an empty string and the action exited with access_key input is required.
The name was correct on eng-1767, which is why it passed there on 24 May, and it changed on merge. Since then: 6 failures, 3 cancelled, 0 successes.
Worth noting the step runs last, after publish has already written to the publish repo. Those red runs still changed production.
| STABLE_VERSION=$(node -p "require('./apps/obsidian/package.json').version" | sed 's/-beta\.[0-9]*//') | ||
| LATEST_N=$(gh release list --limit 50 --json tagName --jq '.[].tagName' \ | ||
| STABLE_VERSION=$(node -p "require('./apps/obsidian/package.json').version") | ||
| LATEST_N=$(gh release list --repo "$PUBLISH_REPO" --limit 100 --json tagName --jq '.[].tagName' \ |
There was a problem hiding this comment.
Bug C: the beta counter never incremented
gh release list ran without --repo, so it queried the monorepo, which has 0 releases and 0 tags. The lookup always came back empty and the code fell through to -beta.1 every time.
The failure mode was the quiet kind: the recomputed version already existed, createGithubRelease took its early return, and the run exited green having shipped nothing.
After the fix the runs produced beta.2 through beta.5, incrementing correctly.
|
|
||
| // Patterns match a whole path segment, never a substring of one, and globs are | ||
| // anchored to a single segment. | ||
| const segmentMatchesPattern = (segment: string, pattern: string): boolean => { |
There was a problem hiding this comment.
Bug D: source files were silently dropped from the publish repo
The old matcher used relativePath.includes(pattern) and turned globs into unanchored regexes, so any file whose name merely contained an excluded word was dropped. "out" ate src/components/DatacoreCallout.tsx. *.log became /.*.log/ and ate src/utils/nativeJsonFileDialogs.ts via "diaLOGs".
Confirmed on a real publish: 101 local source files, 99 published. After the fix, 101 of 101.
Before committing I diffed old against new across the whole apps/obsidian tree: exactly those two files flip to included, nothing flips the other way. Re-checked secrets against the enlarged 113-file set, since loosening an exclusion filter on a public repo is where a leak would hide. Nothing sensitive published.
|
|
||
| // updateLocalVersion runs after the publish-repo push, so the release version | ||
| // has to be written into the staged copy here as well. | ||
| const updateStagedPackageVersion = (tempDir: string, version: string): void => { |
There was a problem hiding this comment.
Bug E: the published package.json was permanently one release behind
updateManifest only rewrote manifest.json in the staging directory. package.json was copied at its pre-bump value, and updateLocalVersion runs after updateMainBranch has already pushed.
Result on both repos:
test repo, right after publishing 9.9.9: manifest.json = 9.9.9 package.json = 1.5.2
publish repo: manifest.json = 1.5.2-beta.1 package.json = 1.5.2
Low user impact, since Obsidian reads manifest.json, but the published repo contradicted itself. Verified on the 9.9.10 publish: both files now read 9.9.10.
| git commit -m "chore: release obsidian ${VERSION} [skip ci]" | ||
| git push | ||
| fi | ||
| # Disabled: the default-branch ruleset requires a pull request and does not |
There was a problem hiding this comment.
Bug E: this step could never succeed on main
The Main Branch Protection ruleset requires a pull request on the default branch and does not list github-actions[bot] as a bypass actor, so the push is always rejected there.
The step sits after publishing, so a real stable release would create the GitHub release, push the publish repo, then fail here and never reach the Linear steps. Retrying does not help: updateMainBranch reports no changes, createGithubRelease skips the existing release, updateLocalVersion rewrites the same files, and the push is rejected again. The Linear half of the pipeline was unreachable for that version.
The two green runs on 24 May prove nothing. They ran on a branch, where git push targets the branch instead of main.
The step's own git diff --quiet guard is what makes disabling it safe: bump the version in the release PR and there is nothing for CI to push. I did not reproduce the rejection, since that needs a real stable release from main.
Obsidian release pipeline: test report
https://www.loom.com/share/ebfdaee0363449a784fb7030c3bb0d02
Six bugs found. Five are fixed in code. Last one about updating versioning on
package.jsonneed discussion.Note for reviewer: Easier to read through Github comments to see the bug overview first, then come back here for more details about testing and fixing of each bug
Terms used below
DiscourseGraphs/discourse-graph-obsidian, the public repo the Obsidian community store and BRAT read fromDiscourseGraphs/discourse-graph-obsidian-test, created for this testingobsidian-main.yamlobsidian-release.yamlHow the tests were set up
publish.tshardcodes the publish repo in three constants. I temporarily pointed those constants and both workflows' credentials at the test repo. Those are the commits on #1311 markedTEMP, each carrying aTODO(ENG-2099): REVERT BEFORE MERGEmarker.I triggered everything from the branch with
workflow_dispatch. That runs the branch's copy of the workflow and script, and still has access to repo secrets:Bug A (fixed): betas published as official releases and overwrote the store's manifest
isExternalRelease()returned true for beta as well as stable.Fixed in 2f7a0ce7. Only a bare
x.y.zcounts as external now, so betas and alphas stay pre-releases and leavemainalone.Verification
I checked the classification against the edited source across fourteen inputs: stable,
-beta.N, bare-beta,-alpha-name,-rc.N, plus malformed ones like1.5,v1.5.2, empty, andabc. All fourteen behaved as intended. Two pre-existing quirks are harmless:1.5.2.1and01.5.2passvalidateVersion, because its regex is not anchored at the end, and then classify as pre-releases instead of being rejected.Then proven in five beta runs. Every beta came out as
prerelease=true:The decisive check is what those five runs did not do. After all of them, the test repo's
mainstill had one commit and one file:No
manifest.json, noRelease v...commit. Before the fix, each of those runs would have overwritten the store's manifest. GitHub also reported no "latest" release while only betas existed, which is the correct signal that a beta is not standing in for a stable release.I confirmed each release's attached
manifest.jsonasset carried the matching version and the right plugin id, by pulling the asset bytes rather than trusting the log:Bug B (fixed): every run on
mainfailed at the Linear stepThe workflows referenced
secrets.OBSIDIAN_LINEAR_RELEASE_KEY. That secret does not exist. The real name isLINEAR_RELEASES_ACCESS_KEY_OBSIDIAN, so the input resolved to an empty string and the action exited:This is why it worked for me and failed for everyone else. The name was correct on the
eng-1767branch, which is why it passed there on 24 May, and it changed when that merged. Outcomes since:Zero successes, ever, on
main.The part worth internalising: that step runs last, after
publishhas already written to the publish repo. Every one of those red runs still changed production. A failed run never meant "nothing shipped". It meant "shipped, then failed to tell Linear".Fixed in all three references in 7b33e2d2, and confirmed live. The key resolves and the step passes.
Bug C (fixed): the beta counter never incremented
gh release listran without--repo, so it queried the monorepo. The monorepo has no releases and no tags. The lookup always came back empty, and the code fell through to-beta.1every time.Fixed in 5eb67213 by pointing the lookup at the publish repo. I verified the shell logic locally against both repos before spending a run on it, then confirmed in sequence. The runs after the fix produced
beta.2,beta.3,beta.4, andbeta.5, each incrementing correctly.Bug D (fixed): source files silently dropped from the publish repo
shouldExcludematched patterns withrelativePath.includes(), and turned glob patterns into unanchored regexes. Any file whose name merely contained an excluded word was dropped."out"ateDatacoreCallout.tsx.*.logbecame/.*.log/and atenativeJsonFileDialogs.tsvia "diaLOGs".Fixed in 1bb10ac7. Patterns now match whole path segments, and globs are anchored to a single segment with regex metacharacters escaped.
Verification
real publish, run 32539711072, commit 77f5b0d7:
I re-ran the secret check against the enlarged set of 113 published files. No
.env, secret,.pem, or credential files..env,.env.branch,.env.example, nested.env.local,*.pem,*.log,node_modules,dist,scripts, and.gitall stay excluded. Names that would previously have been landmines (Logger.ts,Layout.tsx,distance.ts,environment.ts,buildIndex.ts) are correctly kept.Bug E (fixed): the published
package.jsonwas one release behind, permanentlyThere are two copies of the version, written at different times.
updateManifestonly rewrotemanifest.jsonin the staging directory.package.jsonwas copied at its pre-bump value, andupdateLocalVersion, which does bump it, runs afterupdateMainBranchhas already pushed.Observed on both repos:
Low user impact, since Obsidian reads
manifest.json. It mattered because the published repo contradicted itself, and anything readingpackage.jsongot the wrong answer.Fixed in 07180a4d with an
updateStagedPackageVersionstep that runs alongsideupdateManifest, before the push. Verified on the same 9.9.10 publish:Bug F: stable workflow can't run step "Commit version bump"
mainis protected by a ruleset, not by classic branch protection. That is why it reads as protected but the protection endpoint returns 404:github-actions[bot]is not a bypass actor, so theCommit version bumpstep'sgit pushis rejected whenever the workflow runs frommain. The step sits after publishing, so a real stable release would create the GitHub release, push the publish repo, then fail, and never reach the Linear steps.Retrying does not help.
updateMainBranchreports no changes,createGithubReleaseskips the existing release,updateLocalVersionrewrites the same two files, and the push is rejected again. The Linear half of the pipeline was unreachable for that version, permanently.Options:
discourse-graph-obsidianThe stable workflow, tested
I dispatched it twice, both times with a deliberately fake version. That kept it from colliding with anything real, and kept the Linear
completecommand away from the genuine1.5.4release:First run, 32537199051. All ten steps green, the first time this workflow has run since May. It produced release 9.9.9 with
prerelease=falseand all three assets, and GitHub promoted it to "latest". The contrast with the five betas is exactly what we want. The push landed commit 150d5286 with 111 files. This run exposed Bugs D and F.Second run, 32539711072, verifying those two fixes. It produced release 9.9.10, again
prerelease=falsewith all three assets and promoted to "latest", and commit 77f5b0d7 with 113 files. The two extra files are the ones Bug D had been eating.On the security question: across all 113 published files there are no
.env, secret,.pem, or credential files, andpackage.jsonhad itsscriptsblock stripped as intended.Both Linear commands worked in both runs.
syncfound the commit range and synced.completeadvanced the release to Released.7a5dc916-0ee6-4dca-a0fb-2b496d8b18fa, loggingFound 4 matching commits between 8d68b05 and 2dc6982Two things about Linear to flag:
synccreated brand new releases for both9.9.9and9.9.10, each named "New release", andcompletemarked them Released. A typo will invent a release rather than fail.issues [ENG-2099]did not translate into anything. The 9.9.9 release ended withissueCount: 0. Do not read that log line as proof that issue attribution works.Separately, one beta run's Linear sync correctly did nothing:
The only commit in range touched
.github/, which is outsideinclude_paths. That is right, and it is good evidence the path filtering works. It also means the GitHub release and the Linear release can legitimately diverge while the step reports success.Concurrency: fast merges skip betas
Three dispatches fired back to back produced one running, one cancelled, one queued. With
cancel-in-progress: false, GitHub keeps only one pending run per concurrency group, so a third arrival kills the waiting second:The two survivors computed
beta.4andbeta.5correctly, so there is no race in the version lookup. The queue serialises properly.This is the mechanism behind the three
cancelledruns onmain, and it means rapid merges do not each produce a beta. Not a bug, but not obvious either. Worth deciding whether it is what we want.Mine to finish:
TEMPcommits on ENG-2099 Test and fix Obsidian Linear release pipeline #1311 and take it out of draft.apps/obsidian/package.jsonandmanifest.jsonat9.9.10on the branch. This must not merge carrying a9.9.xversion.9.9.9and9.9.10, the eight test repo releases, and the stale Linear release "New release" from 24 May that has no version and still sits In Progress.