name: patch-backstage description: Workflow to backport Backstage changes into RHDH by syncing a downstream maintenance branch and generating yarn patches.
RHDH Patch Generator
Purpose
Ship a fix on an RHDH release-* line without bumping published Backstage versions by adding Yarn patches (.yarn/patches/, package.json resolutions, lockfiles).
COMMITS: SHAs on backstage/backstage that are onmaster(merged fixes). FetchmasterfromBACKSTAGE_UPSTREAM_REMOTEso those objects exist in the maintenance clone forgit show/ cherry-pick.- Build source: redhat-developer/backstage at
patch/release-<VERSION>only (not a separate upstream checkout). - Cherry-pick those SHAs onto maintenance only if the pre-check shows maintenance source still differs; otherwise build + patch RHDH only.
Repos
| Repo | Role |
|---|---|
| redhat-developer/rhdh | Patches live here. Sync release-<VERSION>; run yarn patch here (root and/or dynamic-plugins/). |
| redhat-developer/backstage | Maintenance fork: patch/release-<VERSION>, optional cherry-pick, yarn build per package cd, copy dist/ into RHDH patch temps. PRs from your fork. |
| backstage/backstage | Upstream object source only: remote on the same maintenance clone, git fetch for COMMITS. Do not use a second upstream checkout as the build tree. |
Parameters
| Name | Required | Notes |
|---|---|---|
RHDH_VERSION |
Yes | e.g. 1.9 → release-1.9, patch/release-1.9. Do not infer from the current branch. |
RHDH_ROOT |
No | Absolute path to the RHDH repo root (appears as <RHDH_ROOT> in examples). Inferred from context if omitted. |
COMMITS |
Typical | Upstream SHAs (oldest first for cherry-pick). If no SHAs, manual dist / patch only. |
PACKAGES |
If unclear | @backstage/... names. Derive from COMMITS (below) when paths map cleanly. |
Path map: @backstage/plugin-<id> → plugins/<id>/; other @backstage/<id> → packages/<id>/.
Deriving PACKAGES from COMMITS
In the maintenance clone, after git fetch <BACKSTAGE_UPSTREAM_REMOTE> master so COMMITS exist locally: git show --name-only --pretty=format: <SHA> (union for multiple SHAs). Map plugins/* and packages/* roots; read each package.json name; dedupe. Ignore-only changes (root lockfile, .changeset/, docs/, version-only package.json) → ask which packages to patch.
Example: 66e08b08f94a31cbf28b416c89b61549bc3b64a2 → @backstage/cli-common, @backstage/backend-plugin-api, @backstage/plugin-techdocs-node.
Git remotes, hooks, and where this skill file lives
Map remotes by URL in each clone (git remote -v); never assume upstream means a given org.
- RHDH core (
release-*): URL redhat-developer/rhdh →RHDH_CORE_REMOTE. - Maintenance Backstage (
patch/release-*, fork push): URL redhat-developer/backstage + usually your fork asorigin. - Upstream Backstage (fetch
COMMITSonly): URL backstage/backstage →BACKSTAGE_UPSTREAM_REMOTE.
Exact maintenance tip:git fetch https://github.com/redhat-developer/backstage.git patch/release-<RHDH_VERSION>
then HUSKY=0 git checkout -B patch/release-<RHDH_VERSION> FETCH_HEAD when Husky would otherwise run on checkout.
Silencing hooks: For branch sync only (Steps 1–2: fetch/checkout/pull/checkout -B, and git cherry-pick when you are not relying on hook side effects), prefix with HUSKY=0. Omit HUSKY=0 on git commit if you want lint-staged locally.
cd <RHDH_ROOT> && HUSKY=0 git fetch <RHDH_CORE_REMOTE> release-<V> && HUSKY=0 git checkout release-<V> && HUSKY=0 git pull <RHDH_CORE_REMOTE> release-<V>
Rulesync: Edit SKILL.md only under .rulesync/skills/patch-backstage/ (rulesync expects one directory per skill with SKILL.md inside; a flat *.md at skills/ root is ignored). With "skills" and "simulateSkills": true in rulesync.jsonc, yarn rulesync:generate writes both .claude/skills/ and .cursor/skills/ from that tree (rulesync treats Cursor skill output as “simulated”). Stage and commit generated paths with .rulesync/ after edits.
Agent execution
- Batch related shell commands with
&&andcd <RHDH_ROOT>; cwd may not persist between tool calls. Usenetwork/git_write/allas needed (allforrm/cpinto Yarn patch temps or stubborn sandboxes). - Stop and ask when
RHDH_VERSION,COMMITS, clone paths, or workspace ownership is missing or ambiguous—not for a second confirmation when the user already asked for yarn patches for given SHAs (see Pre-check → patch-only). - Do not invent remotes or wander with speculative
find; do run steps this doc names (git remote -v,yarn why, etc.).
Dist baseline
yarn patchoverlaysdist/on the version RHDH already resolves (lockfile), soPACKAGE_VERSIONmust come fromyarn why, not from “what Backstage released.”- Compile only on redhat-developer/backstage
patch/release-<RHDH_VERSION>after fetching that ref from redhat-developer (local/fork tips can diverge by name). - Do not build from backstage/backstage release tags or other upstream checkouts to “match” versions unless this workflow is explicitly extended.
Workflow (overview)
- Pre-flight: Clean trees and remotes (Step 1 opening + Git remotes); set
RHDH_CORE_REMOTE,BACKSTAGE_UPSTREAM_REMOTE, optionalFORK_REMOTE(your Backstage fork for PRs). - RHDH:
HUSKY=0fetch/checkout/pullrelease-<RHDH_VERSION>. - Maintenance: Fetch
patch/release-*from redhat-developer;HUSKY=0 checkout -B;git fetch <BACKSTAGE_UPSTREAM_REMOTE> master(upstream integration branch forCOMMITS); pre-check; cherry-pick or patch-only path;yarn buildperPACKAGES(cd+yarn build, not rootyarn workspace … build). - RHDH: Remove stale
.patch+resolutionsfor targets;yarn why→ versions;yarn patch/ replacedist/patch-commit; clean upresolutions;yarn install(root anddynamic-plugins/as needed). - Verify:
@patch:in each relevantyarn.lock;yarn whyshowsvia patch:; commit artifacts.
Step 1: Sync RHDH
Pre-flight (both repos): git status clean in RHDH and the maintenance Backstage clone (stash WIP or git merge --abort / git rebase --abort as needed). Do not run the workflow mid-conflict.
git remote -v→RHDH_CORE_REMOTE= remote for redhat-developer/rhdh.HUSKY=0 git fetch … release-<RHDH_VERSION>&&HUSKY=0 git checkout …&&HUSKY=0 git pull …. Fail if the branch is missing.- Set
MAINTENANCE_BRANCH=patch/release-<RHDH_VERSION>(used when opening a Backstage PR).
Step 2: Maintenance clone
One clone with redhat-developer/backstage + backstage/backstage remotes.
2.1 Sync patch/release-*
git fetch https://github.com/redhat-developer/backstage.git patch/release-<RHDH_VERSION> \
&& HUSKY=0 git checkout -B patch/release-<RHDH_VERSION> FETCH_HEAD
Then git fetch <BACKSTAGE_UPSTREAM_REMOTE> master. backstage/backstage lands merged work on master; COMMITS should be reachable from master. Do not check out upstream as the build tree.
2.2 Pre-check (skip cherry-pick when source already matches)
For each SHA in COMMITS (oldest first):
git show --name-only --pretty=format: <SHA>- Drop bookkeeping-only paths: root
package.json,CHANGELOG.md, packagepackage.jsonversion-only edits,.changeset/,docs/, lockfiles, etc. Keepsrc/, tests, fixtures tied to the fix. git diff <SHA> HEAD -- <kept paths>(union paths if multiple SHAs).
Empty diff → maintenance already has the functional fix; do not cherry-pick (avoids changelog/version noise).
2.3 Patch-only vs ask
- Proceed without extra confirmation if the user already asked for yarn patches for
COMMITSon thisrelease-*: empty pre-check → go to 2.6 Build and RHDH Steps 3–5; note in the Final summary that cherry-pick was skipped. - Ask if intent is vague (“sync Backstage” only) or they may want a maintenance PR for traceability despite identical source.
2.4 Cherry-pick (when pre-check was non-empty)
git cherry-pick <SHA> (oldest first) onto patch/release-*.
Conflicts: Prefer the cherry-picked commit’s content for conflicted src/ (during cherry-pick, git checkout --theirs -- <path> refers to that commit). If fixing conflicts would drop the functional fix, or conflicts are only changelog/version noise you should not merge, git cherry-pick --abort, note it in the Final summary, and coordinate with the user. Do not push a broken maintenance branch.
2.5 Push maintenance (optional)
Push to FORK_REMOTE and open a PR to redhat-developer/backstage base MAINTENANCE_BRANCH (no direct push to redhat-developer).
2.6 Build PACKAGES
For each package: cd plugins/<id>/ or packages/<id>/ → yarn build. If dist-types or build fails: maintenance repo root yarn install / yarn tsc, then retry per-package yarn build.
Step 3: Prepare RHDH
dynamic-plugins/ is a separate Yarn project: its yarn.lock and resolutions are independent of the root.
- Remove prior
.patchfiles and matchingresolutionsentries for the packages you are refreshing (search both root anddynamic-plugins/package.json). yarn why @backstage/<pkg>from RHDH root; if empty, run fromdynamic-plugins/. Record PACKAGE_VERSION for Step 4.
Step 4: Generate patches
Run from RHDH root or dynamic-plugins/ depending on where the dependency resolves.
Temp folder: Each yarn patch … prints a new path—use it immediately for rm, cp, yarn patch-commit -s (same shell or paste path). Do not reuse an old temp. Sandboxes may need all for cp into system temp.
Per package:
yarn patch <package>@npm:<PACKAGE_VERSION>rm -rf <PATCH_TEMP>/dist&&cp -r <backstage-clone>/<plugins|packages>/.../dist <PATCH_TEMP>/distyarn patch-commit -s <PATCH_TEMP>in that workspace.
If a package resolves in both trees at the same version, patch both places (or mirror the patch: resolution into dynamic-plugins/package.json and run yarn install there too).
After yarn patch-commit (cleanup — required)
- Replace any bare
"@backstage/foo": "1.2.3"inresolutionswith the singlepatch:locator Yarn printed—do not leave bare semver beside new patch keys (patch may not apply; Step 5 will show plain@npm:). - Delete spurious range keys
patch-commitadded (e.g.@backstage/foo@^1.6.0→ patch built from@npm:1.5.0), which mis-resolve other workspaces (dynamic-plugins/may need@backstage/backend-plugin-api@1.6.0untouched by root). - Install:
yarn installat root and/ordynamic-plugins/;
Step 5: Verify (required)
Incomplete until every patched package shows a patch locator in yarn.lock and yarn why.
yarn installin each touched project.grep '@backstage/<pkg>@patch' yarn.lock(run insidedynamic-plugins/for packages resolved only there).yarn why @backstage/<pkg>in the same directory: must includevia patch:/@…@patch:, not onlyvia npm:<version>.
Optional: Spot-check node_modules/@backstage/<pkg>/dist/.
Commit: .yarn/patches/*.patch, package.json resolutions, yarn.lock (root and/or dynamic-plugins/); PR to RHDH.
Final summary
- Links:
https://github.com/backstage/backstage/commit/<SHA>for eachCOMMITSentry. PACKAGES, patch locations (root vsdynamic-plugins/), notableresolutionskeys.- Cherry-pick skipped vs applied; conflicts/aborts if any.
MAINTENANCE_BRANCH, optional Backstage PR link; RHDH PR intent.RHDH_VERSION.
Safety
- Do not invent
patch/release-*if missing on redhat-developer/backstage. - No blind conflict resolution on cherry-picks; no direct push to redhat-developer remotes without process.
Common issues (quick reference)
| Symptom | What to do |
|---|---|
| Wrong maintenance tip | Fetch https://github.com/redhat-developer/backstage.git patch/release-<V>, checkout -B … FETCH_HEAD |
bad object on cherry-pick / show |
git fetch <BACKSTAGE_UPSTREAM_REMOTE> master |
| Checkout/pull fails after Yarn / Husky | HUSKY=0 on those git commands |
yarn why / lockfile: no @patch: |
Remove bare semver resolutions for that pkg; use one patch: locator; drop wrong range keys; reinstall |
| Build fails (missing types) | Maintenance root yarn install / yarn tsc, then cd package yarn build |
| Patch wrong version | Use yarn why version, not RHDH meta-version |
| Skill not in Cursor | .rulesync/skills/ may not sync to .cursor/—see Rulesync / Cursor |