Certified Backstage Associate study materials
Certified Backstage Associate study resources

The 38-Minute Pull Request

Certified Backstage Associate · Put your knowledge to work in context.

All case studies

The 38-Minute Pull Request

Acme's Backstage monorepo has grown to 62 workspace packages: 14 frontend plugins, 9 backend plugins, 4 shared libraries, and the app/backend packages. A pull request currently runs yarn tsc, yarn lint, yarn test, and yarn build across the whole workspace. Median feedback is 38 minutes: 14 minutes for type-checking, 22 for Jest, 6 for lint, and 9 for builds, with some overlap. The team merges about 120 PRs a week and has started batching changes to avoid CI. Eight percent of test runs fail on timing-sensitive tests, so engineers re-run the whole pipeline. A proposal to split the monorepo into five repositories was rejected because shared libraries change often and cross-repo versioning caused problems last year. The platform team wants p50 feedback under 15 minutes without giving up full validation before release. They use GitHub Actions, Yarn workspaces, Changesets, and the Backstage CLI.

1. What is the first change you would make to cut pull-request feedback time, and how would you keep the main branch protected? Be specific about which checks run where.

Read the worked answer

Start by separating merge-gating checks from full validation. On PRs, run backstage-cli repo lint, repo test, and repo build with --since origin/main rather than every workspace, cache Yarn and TypeScript build information, and shard Jest by package across parallel runners. Keep a full type-check, test, and build on main plus a nightly job, and require the full job for release branches. Verify the CLI version's --since semantics: it should select changed packages, but confirm whether it also selects reverse dependencies. If it does not, add a reverse-dependency step for shared packages. For flaky tests, quarantine by name with an owner and a deadline rather than letting reruns hide them. The 15-minute target is realistic only if type-checking and tests are split across runners; a single runner still serializes. Measure p50 and p95 separately, because a fast median with a 40-minute tail still encourages batching.

2. A PR changes the shared `packages/core-app-api` package. Affected detection selects only three packages, but about twenty packages depend on it transitively. What do you do before trusting the result?

Read the worked answer

The three-package result is suspicious because core-app-api is a shared library; most plugins depend on it directly or transitively. Do not accept the selection until you inspect the workspace dependency graph and TypeScript project references. Run a full type-check on a branch that changes only that package and compare failures against the affected-only job. If dependents are missed, either fix the selection to include reverse dependencies or fall back to full type-check whenever a package under packages/ changes. Tests can be narrower: run tests for direct dependents plus contract tests for the shared package. Builds should include all dependents because declaration output can break consumers. Add a CI self-test that changes a shared package in a scratch branch and asserts the expected package set; otherwise the optimization silently reduces coverage while looking successful.

3. How should Changesets be configured so internal packages version together but nothing is accidentally published to npm?

Read the worked answer

Mark packages that must never publish as private: true, and configure Changesets so versioning is allowed but tagging or publishing private packages is disabled through the privatePackages settings in .changeset/config.json. Use changeset status --since main as a required check so user-facing changes have a changeset, while allowing empty changesets for docs or CI-only work. Run changeset version on a release branch or a bot PR, not on every feature PR, to avoid constant merge conflicts. Decide whether all internal packages share one version or version independently; shared libraries usually need a fixed group so dependency ranges stay coherent. Do not run changeset publish in CI unless you actually publish; instead tag the release commit. This preserves internal versioning without creating an accidental public release path.