fix(github-actions-grafana-jump): read a PR page's jump-links config from the PR's own branch - #55
Open
nsheaps-oura wants to merge 2 commits into
Open
fix(github-actions-grafana-jump): read a PR page's jump-links config from the PR's own branch#55nsheaps-oura wants to merge 2 commits into
nsheaps-oura wants to merge 2 commits into
Conversation
Userscript Version PreviewPreview only — versions and CHANGELOGs are bumped automatically on merge to
|
…from the PR's own branch A PR page kept reading .github/jump-links.config.yaml from the default branch, so a config added on a branch didn't show up on the pull request proposing it - the one page you're most likely to be looking at while trying a config change out before merging it. Branch-aware config (#51) skipped the PR page because a PR's head branch isn't in its URL, and resolving it looked like it needed an API call. It doesn't: GitHub's PR header already states the merge in full ("wants to merge N commits into <base> from <head>"), with each branch as a link to its own tree. That's read from the live DOM the same way a workflow run's branch already is (#53), reusing that change's tree-href parsing - which is why branchFromRunTreeHref is now branchFromTreeHref, shared by both headers. - A fork PR's head branch is in the contributor's own repo, so the shared href check rejects it and the config keeps coming from the default branch, matching what a fork-triggered run page already does. - A merged or closed PR's head branch is usually deleted, so a PR target falls back to the default branch when its own ref has no config, rather than leaving the page with fewer links than before. Verified live against the PR headers on jouzen/android#28338 (open draft, same-repo), #51 (merged, head branch deleted) and #54, and cli/cli#14108 (fork). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeMSMf28QJFLTiQ25sVM3S
… on fallBackToDefaultBranch too currentRepoConfigKey was built from repoConfigCacheKey(), which only names org/repo/branch. A file view on branch X (strict, no fallback) and a PR whose head branch is also X (fallback allowed) produced the same key, so navigating between them skipped the required re-resolve and left the wrong config displayed in both directions. Add repoConfigTargetKey() to fold fallBackToDefaultBranch into the gate key, and use it in checkLocation() instead of the bare cache key. Also tighten prHeadBranchHref()'s head-vs-base check: match the "copy branch name" sibling by its data-component="IconButton" rather than any <button>, and walk only the link's later siblings via nextElementSibling instead of treating the link as one of its own siblings. Flagged by an automated review on #55 (P1 correctness bug, plus two P2 nits addressed here).
nsheaps-oura
force-pushed
the
n8bot/pr-page-head-branch-config
branch
from
August 12, 2026 04:40
00d059d to
5f03090
Compare
There was a problem hiding this comment.
Summary
All three findings from v1 addressed in 5f03090, cleanly and tightly-scoped. Approving.
Findings (0 open)
No new findings. All prior findings resolved — see the follow-up commit 5f03090 and the "Verification of the fix" section below.
Verification of the fix
P1 — currentRepoConfigKey shared across strict/fallback targets (prior comment):
- New
repoConfigTargetKey()(src/index.ts:1256-1259) foldsfallBackToDefaultBranchinto the gate key by appending+fallback— the two Scenario A/B page transitions from the prior comment now hit therepoKey !== currentRepoConfigKeypath and re-resolve as required. checkLocation()(src/index.ts:2548) usesrepoConfigTargetKey(repoCtx)instead of the bare cache key.- The
repoConfigTargetKey's own docstring cross-referencescheckLocation()and spells out the exact file-view-vs-PR scenario, so the entanglement is on the record where a future reader will see it (resolving prior note #4 too). - Tests: the new
repoConfigTargetKey tells apart a strict target from a fallback one at the same branchtest pins the key contract, andcross-navigation between a file view and a same-branch PR fetches config differently each wayexercises the module-levelrepoConfigCachestate that made the leak reachable — walking file → PR → file at the same branch and checking each direction returns the correct result. Both tests explicitly name Scenario A/B in comments, which will save the next reviewer a hop. - Fetch-behavior guard: the
checkLocation()in-flight-response check (if (currentRepoConfigKey !== repoKey) return;) still closes over the new fallback-awarerepoKey, so a mid-flight navigation from strict→fallback (or vice versa) at the same branch drops the stale result rather than applying it — no new race introduced.
P2 — head-vs-base heuristic hardening (prior comment):
prHeadBranchHref()(src/index.ts:2262-2270) now matches the sibling ondata-component="IconButton"rather than any<button>, so a future unrelated<button>GitHub adds beside the link (toolbar, kebab, etc.) won't false-positive.- The document-wide
a[data-component="BranchName"]selector is untouched — a reasonable defer, given "first match wins" still lands on the header (page + sticky both state the same branch, and no other BranchName-with-IconButton-sibling exists on a PR page today). The narrower sibling check is the higher-value half of the pair.
P2 nit — sibling loop (prior comment):
- Replaced with the
nextElementSiblingwalk. As a bonus, only later siblings are checked, which correctly mirrors the DOM order shown in the comment above the function (button always follows the anchor) — tighter than what I originally suggested.
How I evaluated
- Read the second commit's diff (
5f03090) end-to-end against the v1 findings, then re-read the surrounding context (RepoConfigTargetdocstring,fetchRepoConfigForTarget,checkLocation()'s in-flight-fetch guard) to check the fix doesn't create a new gap. - Traced the two Scenario A/B page transitions from the prior P1 comment against the new
repoConfigTargetKey()to confirm the gate now sees them as different targets. - Read the two new tests (
repoConfigTargetKey tells apart…,cross-navigation between a file view and a same-branch PR…) and traced the module-levelrepoConfigCachestate across the three sub-fetches to confirm the third-call assertion (nullagain) actually depends on the fix rather than passing by coincidence. - Confirmed nothing else in the diff changed since v1 (only the second commit's four localized edits + two tests), so the v1 strengths still stand — not repeating them.
- Did not run the test suite; findings are from static reading of the diff and the PR's declared test results.
🤖 Automated review · Claude Opus 4.7 · job
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
Branch-aware repo config (#51) reads
.github/jump-links.config.yamlat the ref you're looking at, but it deliberately left the PR page out: a PR's head branch isn't in its URL, and resolving it looked like it needed an API round-trip. So a PR page always read the default branch's config — on the one page you're most likely to be on while trying a config change out before merging it.That's now a live problem: two open draft PRs (jouzen/android#28338, jouzen/ios#32201) add a config file that only exists on their own branch, and their PR pages showed nothing.
Reproduced live on jouzen/android#28338: the URL the old code builds is
.../jouzen/android/HEAD/.github/jump-links.config.yaml, and that path doesn't exist onmainyet (gh api ... ?ref=main→Not Found), while the branch's own copy has aprpage entry ("CI traces for this PR").What it does now
No API call needed — GitHub's PR header already states the whole merge, with each branch as a link to its own tree. That gets read from the live DOM the same way a workflow run's branch already is (#53), reusing that change's tree-href parsing.
branchFromRunTreeHrefis therefore nowbranchFromTreeHref, shared by both headers.flowchart TD A["/org/repo/pull/123"] --> B["head-branch link in the PR header"] B --> C{"is its href a branch<br/>of this same repo?"} C -->|yes| D["read the config at that branch"] C -->|"no (fork PR)"| E["read the default branch's, as before"] C -->|"header hasn't rendered yet"| F["default branch; re-checked on<br/>the next DOM mutation"] D --> G{"does that branch<br/>have a config?"} G -->|yes| H["use it"] G -->|"no (e.g. deleted after merge)"| EWhich element, and why that one
The head branch is one whole href, so unlike the
/blob/<ref>/<path>URLs #53 had to disambiguate, a slashed branch name needs no special handling here. What the header actually renders, captured from #54's own page:The base branch is the same component rendered right next to it, so which one is the head is decided structurally, not by position: only the head branch has the "copy branch name" button beside it, as an element sibling of the link. Matched on
data-component, Primer React's own attribute — the class names around it (PullRequestBranchName-module__branchName__SCtl2) are per-build hashes and aren't used.Fork PRs and merged/closed PRs
/loganrosen/cli-1/tree/.../pull/51/changes)A fork PR is skipped for the same two reasons the run page already skips a fork-triggered run: that ref doesn't exist in the repo being browsed, and a fork's copy of the config isn't this repo's config to read — anyone can open a fork PR, so honoring one would let any contributor decide what buttons the page offers.
A merged or closed PR's head branch is usually deleted, and insisting on it would leave those pages with no repo config where before they had the default branch's. So a PR target is the one target marked
fallBackToDefaultBranch: if its own ref has no config, the default branch's is read instead. Both reads share the existing per-{org, repo, ref}cache, so the fallback costs one extra request per repo, not one per pass. Pages whose own URL names a ref keep #51's stricter rule — that ref's version is the answer, including when it hasn't got one.Verification
Live, in the browser (real github.com pages, logged in): ran the compiled DOM-reading logic (
prHeadBranchHref→branchFromTreeHref→repoConfigUrl) against each page in the table above and confirmed the resolved branch and the resulting raw URL. On jouzen/android#28338 it resolvesn8bot/jump-links-configand buildshttps://raw.githubusercontent.com/jouzen/android/n8bot/jump-links-config/.github/jump-links.config.yaml, where the old code built theHEADURL that 404s. Also confirmed the header renders the pair twice (page header + the sticky one when scrolled) and both state the same branch.Code-correct:
yarn test— 83/83 pass, including new coverage for a PR head href (slashed name), a fork PR's href, and the threefetchRepoConfigForTargetfallback cases (falls back on a 404, doesn't when the ref has its own config, doesn't at all for a ref the URL itself named).yarn install --immutable,yarn build,yarn lintclean.Not verified: the finished userscript running as an installed script on a PR page. Same two blockers #54 documents — github.com's CSP blocks both loading the built script into the page and any page-context fetch to
raw.githubusercontent.com(which is exactly why the script usesGM.xmlHttpRequest), and installing a dev build into the browser's script manager would change local browser config. So the config-fetch half was checked out of band (gh apifor the branch's file and its absence onmain,curlfor the 404 a deleted branch gives) rather than through the script itself.Co-Authored-By: Claude noreply@anthropic.com
https://claude.ai/code/session_01LeMSMf28QJFLTiQ25sVM3S