feat: Upgrade Backstage to 1.53.1 - #40
Merged
Merged
Conversation
Upgrades Backstage from 1.49.2 to 1.53.1. Template changes between the two versions: https://backstage.github.io/upgrade-helper/?from=1.49.2&to=1.53.1 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The lint rules shipped with @backstage/cli 0.36.4 now flag the missing blank line after the import in the Playwright e2e test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The upstream template pinned `jest` and `@jest/environment-jsdom-abstract` to `~30.2.0` because later 30.x releases regressed on Node 22, which is one of the Node versions this repo supports. See @backstage/create-app 0.8.3: https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The frontend now takes longer to become interactive on a cold start, so upstream raised the assertion and dev-server timeouts and switched the app web server from a `port` check to a `url` check. Waiting on the URL means Playwright waits for the dev server to actually serve a response rather than just for the port to be bound. See @backstage/create-app 0.9.0: https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Matches the create-app template. `npmMinimalAgeGate: 3d` refuses to install package versions published less than three days ago, which blunts the npm account-takeover attacks that publish a malicious patch release and rely on it being pulled in within hours. `@backstage/*` is preapproved so that Backstage's own releases are still installable on the day they land. Note this means dependabot PRs that bump a dependency to a brand new release will fail to install until that release is three days old. Yarn is resolved through corepack from the `packageManager` field, which the Dockerfile already enables, so no image changes are needed. See @backstage/create-app 0.8.4: https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The 1.53 create-app template adds a user-settings plugin on both sides, which this repo was missing. It is two halves that only work together: - `@backstage/plugin-user-settings-backend` serves the settings over HTTP - `@backstage/plugin-app-module-user-settings` overrides the frontend `storageApiRef` with `UserSettingsStorage`, which talks to that backend Without the frontend module the storage API falls back to `WebStorage`, so things like the chosen theme, starred entities and sidebar pin state live in browser localStorage and are lost when a user switches browser or device. With both halves they are stored per-user in Postgres and synced across open tabs via the signals plugin, which is already enabled here. The frontend module needs no wiring in App.tsx; it is picked up by the `app.packages: all` discovery already configured in app-config.yaml. See @backstage/create-app 0.9.0: https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the MCP feature set from the 1.53 create-app template so agents can query the software catalog: - `@backstage/plugin-mcp-actions-backend` serves the MCP endpoint - `@backstage/plugin-auth` provides the OIDC consent page at /oauth2/authorize/:sessionId that clients hit when registering The server is exposed at /api/mcp-actions/v1/readonly rather than the default /api/mcp-actions/v1, because filters are only configurable on named servers. The unnamed path returns 404. Nothing reachable over MCP can mutate state. `scaffolder` is left out of `backend.actions.pluginSources` so no software template can be run, and the `catalog` source is filtered on the `readOnly` attribute that each action declares, since that source also ships `register-entity` and the destructive `unregister-entity`. Filtering on the attribute rather than on action ids means a write action added upstream later is not silently exposed. Verified against a running backend, tools/list returns only: auth.who-am-i, catalog.get-catalog-entity, catalog.get-catalog-model-description, catalog.query-catalog-entities, catalog.validate-entity `auth.clientIdMetadataDocuments` is enabled so clients can register themselves and get a token through an interactive approval popup instead of everyone sharing one static MCP_TOKEN. Note this currently accepts any client id and redirect URI; see the commented patterns in app-config.yaml to narrow that. See https://backstage.io/docs/ai/mcp-actions Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comparing both config files against the 1.53 create-app template turned up several sections we simply do not have. Absence is ambiguous: it does not say whether we decided against something or never noticed it. Copy each one in commented out with the reason, so a future template diff shows only real gaps. app-config.yaml: - catalog.import, we rely on the defaults, which match the template values - catalog.rules and catalog.locations, our entities come from the GitHub discovery providers rather than a static list, and catalog-extra is picked up by the `extraCatalogs` provider's catalogPath - kubernetes, we have no cluster to surface and the plugins are not installed - the GitHub Enterprise example, plus a note on why we authenticate with a GitHub App rather than the template's personal access token app-config.production.yaml: - auth.providers.guest, which the template re-enables in production. We must not: backstage.openedx.org is public and sign-in has to go through GitHub. This is the one divergence where silently following the template would be a security regression, so it is worth stating outright. - the catalog.locations override, which only exists to replace the example entities that we never seed No effective config change; verified with `config:check --strict` and by diffing `config:print` output. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Upstream vendors the Yarn release at `.yarn/releases/yarn-4.13.0.cjs` and points `yarnPath` at it, which gets integrity for free because the binary is in git. We resolve Yarn through corepack instead, a deliberate choice from 0c50d8f, and `packageManager: "yarn@4.13.0"` carried no hash, so corepack would install whatever the registry served for that version. That sat badly next to the `npmMinimalAgeGate` hardening added a few commits ago, which exists to defend against exactly that. Corepack verifies the `+sha512` suffix when it downloads, which is the case that matters: a fresh CI or Docker build. Confirmed by pointing COREPACK_HOME at an empty directory with a deliberately wrong hash and watching it refuse with a mismatch error. Also drop `COPY .yarn ./.yarn` from the Dockerfile. That line is only meaningful upstream, where there is a vendored release to copy. Nothing under `.yarn` is tracked here, and .dockerignore excludes the `install-state.gz` that is the only thing which ever appears there, so it copied an empty directory. Worse, it only worked at all because the documented build order runs `yarn install` first and that creates the directory as a side effect. On a clean checkout the build failed on a missing path. Verified the image still builds without it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
feanil
force-pushed
the
feanil/backstage_1_53_upgrade
branch
from
August 14, 2026 15:34
2d6f1ef to
595ca49
Compare
Contributor
Author
|
Reviewed against https://backstage.github.io/upgrade-helper/?from=1.49.2&to=1.53.1&yarnPlugin=0 and looks good to me. |
feanil
marked this pull request as ready for review
August 14, 2026 15:46
`versions:bump` only moves version numbers. Everything the create-app template owns has to be reconciled by hand, and this upgrade turned up two ways of getting that wrong: diffing the old template against the new one rather than against ours, which hides sections that were never adopted here in the first place, and diffing `templates/default-app` when we are on the new frontend system and want `templates/next-app`. Both made real additions look like migration noise we had already dealt with. Also record that type checks alone do not catch broken plugin wiring, that `yarn test` hangs in watch mode without CI=1, and that a backend package is usually half of a pair and always needs registering. While here, correct two things this file had wrong: - it described app-config.production.yaml as the production config, when production loads both files and merges them, so anything in app-config.yaml is live in production too - catalog-extra/ is picked up by the `extraCatalogs` provider's catalogPath, not by a `catalog.locations` entry, which does not exist Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The command change with one of the updates.
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.
Upgrades Backstage from 1.49.2 to 1.53.1, and syncs the parts of the
@backstage/create-apptemplate that we had drifted from.Template changes between the two versions:
https://backstage.github.io/upgrade-helper/?from=1.49.2&to=1.53.1
Commits
choreyarn backstage-cli versions:bump— the upgrade itselffixbuild~30.2.0; later 30.x regressed on Node 22buildport→urlfor the app dev serverbuildfeatfeatdocsBreaking changes reviewed
Four packages were flagged as having breaking changes. None required code
changes here:
plugin-scaffolder-backend3.2.0 → 4.0.2 only dropped deprecated oldbackend system types, which we never imported
backend-defaults0.16 → 0.17.6 has none in that rangeplugin-auth-node0.7.0 only affects explicitSignInResolverFactoryOptionstype parameters, which we do not use
frontend-plugin-apimovedNavContentBlueprintandSignInPageBlueprintto
@backstage/plugin-app-react, which we had already done in the previousupgrade
New behaviour worth a closer look
Yarn age gate.
npmMinimalAgeGate: 3drefuses package versions publishedless than three days ago, which blunts npm account-takeover attacks that
publish a malicious patch release.
@backstage/*is preapproved so Backstagereleases still install on day one. Side effect: dependabot PRs bumping to a
brand new release will fail to install until it is three days old.
MCP is read-only.
scaffolderis deliberately left out ofbackend.actions.pluginSources, so no software template can be run by anagent. The
catalogsource also shipsregister-entityand the destructiveunregister-entity, so the server filters on thereadOnlyattribute eachaction declares rather than on action ids — a write action added upstream
later will not be silently exposed. Verified against a running backend,
tools/listreturns only:Because filters are only configurable on named servers, the endpoint is
/api/mcp-actions/v1/readonly. The unnamed/api/mcp-actions/v1returns 404.auth.clientIdMetadataDocumentsis enabled so clients can register themselvesand get a token through an interactive approval popup instead of everyone
sharing one static
MCP_TOKEN. This currently accepts any client id and anyredirect URI, which is the upstream default; see the commented patterns in
app-config.yamlif we want to narrow it.User settings now live in Postgres. Previously the storage API fell back to
browser localStorage, so theme and starred entities did not follow a user
between devices. This needed both halves — the backend plugin alone does
nothing without the frontend module.
Config that is intentionally off
Both
app-config.yamlandapp-config.production.yamlwere compared againstthe full 1.53 template, not just the diff between template versions. Sections
we do not use are now copied in commented out with a reason, so a future
template diff shows only real gaps.
The one to look at is
auth.providers.guestin the production config. Thetemplate re-enables guest in production; we cannot adopt that as-is, because
packages/backend/src/index.tsstill registers the allow-all permissionpolicy and guest would therefore get anonymous write access. Public
read-only browsing is something we do want for an open source community
portal, and it will land separately alongside a permission policy that
restricts guests to
read.Verification
yarn tsc,yarn test,yarn lint:all,yarn build:all,yarn install --immutableandyarn build-imageall pass.config:check --strict --deprecatedpasses for both the development andproduction config layers, and the backend was booted locally to confirm all
plugins wire up and the MCP tool list is what it claims to be.
Two pre-existing issues were left alone as out of scope:
yarn prettier:checkfails on 24 files (it fails on
maintoo), and the e2e test asserts on'My Company Catalog'rather than this instance's title.