Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ The OpenChoreo plugin set is tested against a specific Backstage release line. I

## Tested combination

| Component | Version |
| ---------------------- | ------------ |
| Backstage release line | **1.51.0** |
| Node.js | 20.x or 22.x |
| Yarn | 4.13.x |
| `@backstage/cli` | 0.36.x |
| OpenChoreo plugin set | `1.2.x` |
| Component | Version |
| ----------------------- | ------------ |
| Backstage release line | **1.51.0** |
| `@backstage/create-app` | 0.8.3 |
| Node.js | 22.x or 24.x |
| Yarn | 4.4.1 |
| `@backstage/cli` | 0.36.x |
| OpenChoreo plugin set | `1.2.x` |

`@backstage/create-app@0.8.3` is the scaffolder release that produces Backstage `1.51.0`, and the Yarn version listed is the one that scaffold ships in `.yarn/releases/`. Node 20 is **not** supported — the scaffold declares `"engines": { "node": "22 || 24" }`.

If you scaffold at a newer Backstage release and use `versions:bump --release 1.51.0` to come down, your Yarn version will be whatever that newer scaffold shipped (4.13.x at time of writing) rather than 4.4.1. Both work.

## Required `resolutions`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,49 @@ OpenChoreo plugins are published to **GitHub Packages** under the [`@openchoreo`

:::

:::tip Tracking the upcoming 1.2.0 release
:::tip Tracking prereleases

The install commands on this page reference `@openchoreo/<pkg>@^1.2.0`, which will be the GA dist-tag of the next plugin release. While `1.2.0` is still under active development, install via the `next` dist-tag to get the latest prerelease today:
The install commands on this page reference `@openchoreo/<pkg>@^1.2.0`, which resolves to the newest stable `1.2.x` release. Stable releases are published under the `latest` dist-tag; prereleases go to `next`. To track the cutting edge instead:

```bash
yarn workspace app add @openchoreo/backstage-plugin@next
```

Once `1.2.0` GA is announced, swap `@next` for `@^1.2.0` to pin to the stable release.

:::
Comment on lines +24 to 32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find every documented package range that can escape the tested 1.2.x line.
rg -n '`@openchoreo/`[^[:space:]`]+@\^1\.2\.0' docs

Repository: openchoreo/openchoreo.github.io

Length of output: 6626


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- installation page: compatibility and range guidance ---'
rg -n -C 5 'compatib|1\.2\.x|1\.2\.0|\^1\.2\.0|~1\.2\.0|latest|next|stable|prerelease' \
  docs/platform-engineer-guide/backstage-plugins/installing-into-existing-backstage.mdx

echo '--- migration page: compatibility and range guidance ---'
rg -n -C 4 'compatib|1\.2\.x|1\.2\.0|\^1\.2\.0|~1\.2\.0|latest|next|stable|prerelease' \
  docs/platform-engineer-guide/backstage-plugins/migration-1.1-to-1.2.mdx

echo '--- repository version policy references ---'
rg -n -i -C 3 'compatibility matrix|supported versions|version matrix|semver|dist-tag|backstage-plugin' \
  docs README.md .github 2>/dev/null | head -n 300

echo '--- standalone semver boundary check ---'
python3 - <<'PY'
from functools import total_ordering

`@total_ordering`
class V:
    def __init__(self, s):
        self.t = tuple(map(int, s.split('.')))
    def __lt__(self, other): return self.t < other.t
    def __eq__(self, other): return self.t == other.t
    def __repr__(self): return '.'.join(map(str, self.t))

lower = V("1.2.0")
upper = V("2.0.0")
for raw in ("1.2.0", "1.2.9", "1.3.0", "1.9.9", "2.0.0"):
    v = V(raw)
    print(raw, "satisfies ^1.2.0:", lower <= v < upper,
          "satisfies ~1.2.0:", V("1.2.0") <= v < V("1.3.0"))
PY

Repository: openchoreo/openchoreo.github.io

Length of output: 50389


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- compatibility matrix ---'
matrix=$(fd -i -t f 'compatibility-matrix' docs | head -n 1)
printf 'file: %s\n' "$matrix"
wc -l "$matrix"
cat -n "$matrix"

echo '--- exact affected command blocks ---'
sed -n '160,198p' docs/platform-engineer-guide/backstage-plugins/installing-into-existing-backstage.mdx
sed -n '700,715p' docs/platform-engineer-guide/backstage-plugins/installing-into-existing-backstage.mdx
sed -n '760,775p' docs/platform-engineer-guide/backstage-plugins/installing-into-existing-backstage.mdx
sed -n '816,831p' docs/platform-engineer-guide/backstage-plugins/installing-into-existing-backstage.mdx
sed -n '12,80p' docs/platform-engineer-guide/backstage-plugins/migration-1.1-to-1.2.mdx

echo '--- semver boundary probe ---'
python3 - <<'PY'
def v(s):
    return tuple(int(x) for x in s.split("."))

tests = ("1.2.0", "1.2.9", "1.3.0", "1.9.9", "2.0.0")
for item in tests:
    x = v(item)
    caret = v("1.2.0") <= x < v("2.0.0")
    tilde = v("1.2.0") <= x < v("1.3.0")
    print(f"{item}: ^1.2.0={caret}, ~1.2.0={tilde}")
PY

Repository: openchoreo/openchoreo.github.io

Length of output: 16683


Restrict OpenChoreo package ranges to the tested minor.

The compatibility matrix supports only plugin set 1.2.x, but ^1.2.0 accepts 1.3.0 through <2.0.0. Use ~1.2.0 or 1.2.x in all OpenChoreo install and upgrade commands, including the migration guide.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs/platform-engineer-guide/backstage-plugins/installing-into-existing-backstage.mdx`
around lines 24 - 32, Update all OpenChoreo install and upgrade commands in this
guide, including the migration guide, to constrain package versions to the
tested 1.2.x minor by replacing caret ranges such as ^1.2.0 with ~1.2.0 or
1.2.x. Preserve the separate `@next` prerelease command.


## 1. Prerequisites

- A Backstage workspace on the [supported Backstage version](./compatibility-matrix.mdx). This guide pins to **Backstage `1.51.0`**.
- The workspace must be scaffolded with the **default NFS scaffold**: `npx @backstage/create-app@latest`. (Do NOT pass `--legacy`. If you must stay on legacy, see [Section 9](#9-legacy-frontend-system-fallback).)
- Node.js **20 or 22**, Yarn **4.13.x** (the current `create-app` scaffold ships Yarn 4.13.0 in `.yarn/releases/`).
- The workspace must be scaffolded with the **default NFS scaffold** (do NOT pass `--legacy`; if you must stay on legacy, see [Section 9](#9-legacy-frontend-system-fallback)).
- Node.js **22 or 24** — the scaffold declares `"engines": { "node": "22 || 24" }`, so Node 20 will not work.
- Yarn **4.4.1** if you scaffold at `1.51.0` as described below. (`create-app@latest` ships a newer Yarn — see [Section 3](#authenticate-to-github-packages).)
- Access to a running OpenChoreo control plane (local `k3d` or a deployed cluster).
- OAuth client credentials for the OpenChoreo Identity Provider (used by the catalog sync and user sign-in). On `k3d` the helm chart pre-seeds these; for a deployed cluster see [Identity configuration](../identity-configuration.mdx).

:::tip Backstage version
:::tip Getting Backstage 1.51.0 without a downgrade

`create-app@latest` gives you whatever Backstage release is current, which is newer than the tested `1.51.0`. Pin the scaffolder to the matching release instead, and there is nothing to undo afterwards:

```bash
npx @backstage/create-app@0.8.3
cat backstage.json # -> { "version": "1.51.0" }
```

`@backstage/create-app@0.8.3` is the release that ships Backstage `1.51.0`. If you already have an app on a different release line, use `versions:bump` instead — see [Section 2](#2-pin-backstage-versions).

`create-app` has no `--name` flag, so the command prompts for one. To script it: `printf 'my-app\n' | npx @backstage/create-app@0.8.3 --path ./my-app`.

:::

:::info Optional tab packs need cluster-side planes

If you plan to install the Observability (Section 5) or CI/Build and Workflows (Sections 6–7) packs, the corresponding OpenChoreo planes must exist in the cluster. On the `k3d` quick start they are opt-in:

```bash
./install.sh --with-observability --with-build
```

`create-app@latest` will likely give you Backstage `1.52.x` or newer at the time you read this. After scaffolding, run `yarn backstage-cli versions:bump --release 1.51.0` (covered in [Section 2](#2-pin-backstage-versions)) to align with the tested combination.
Without them the tabs install and render, but have no data behind them.

:::

Expand All @@ -70,32 +90,60 @@ yarn backstage-cli versions:bump --release 1.51.0
yarn install
```

…to align before adding the OpenChoreo packages.
…to align before adding the OpenChoreo packages. `versions:bump` handles downgrades as well as upgrades, so this works from a newer release line too.

:::note

If you are creating a brand-new app, prefer `create-app@0.8.3` ([Section 1](#1-prerequisites)) over scaffolding at the latest release and bumping down. Scaffolding at the right version keeps the `@backstage/*` version churn out of your first commit, so the diff that adds OpenChoreo contains only OpenChoreo changes.

:::

## 3. Authenticate to GitHub Packages {#authenticate-to-github-packages}

GitHub Packages requires authentication even for `read:packages`-only operations. Create a [classic Personal Access Token](https://github.com/settings/tokens/new) with the `read:packages` scope, then wire it into your package manager.

**Yarn 4 (Berry)** — the current `create-app` scaffold's `.yarnrc.yml` enables a 3-day [npm minimum-age gate](https://yarnpkg.com/configuration/yarnrc#npmMinimalAgeGate) that blocks newly published packages. Update `.yarnrc.yml` to both add the `@openchoreo` scope auth **and** pre-approve the scope so fresh OpenChoreo releases install immediately:
**Yarn 4 (Berry)** — add the `@openchoreo` scope to `.yarnrc.yml`. Keep the `nodeLinker` and `yarnPath` lines your scaffold already generated and append the `npmScopes` block:

```yaml title=".yarnrc.yml"
nodeLinker: node-modules
npmMinimalAgeGate: 3d
npmPreapprovedPackages:
- "@backstage/*"
- "@openchoreo/*"

yarnPath: .yarn/releases/yarn-4.13.0.cjs
# Leave this at whatever your scaffold generated. The Backstage 1.51.0 scaffold
# ships Yarn 4.4.1; newer scaffolds ship a newer release.
yarnPath: .yarn/releases/yarn-4.4.1.cjs

npmScopes:
openchoreo:
npmRegistryServer: "https://npm.pkg.github.com"
npmAlwaysAuth: true
npmAuthToken: "${GITHUB_PACKAGES_TOKEN}"
npmAuthToken: "${GITHUB_PACKAGES_TOKEN:-}"
```

…then `export GITHUB_PACKAGES_TOKEN=<your-pat>` before running `yarn install`. Berry expands the `${...}` placeholder from the environment so the token never lands in the repo.

:::warning Keep the `:-` in `${GITHUB_PACKAGES_TOKEN:-}`

Yarn expands `.yarnrc.yml` variables on **every** invocation, not just `yarn install`. With a bare `"${GITHUB_PACKAGES_TOKEN}"`, any yarn command run without the variable exported — `yarn tsc`, `yarn start`, `yarn lint` — aborts before doing anything:

```text
Usage Error: Environment variable not found (GITHUB_PACKAGES_TOKEN) in /path/to/.yarnrc.yml
```

The `:-` suffix supplies an empty default, so day-to-day commands work unauthenticated and only install/fetch needs the real token.

:::

:::note Newly published releases and the minimum-age gate

Scaffolds newer than Backstage 1.51.0 set a 3-day [npm minimum-age gate](https://yarnpkg.com/configuration/yarnrc#npmMinimalAgeGate) in `.yarnrc.yml`. It only blocks packages published within the last three days, so it rarely affects a normal install. If you are installing an OpenChoreo release that fresh and Yarn refuses it, pre-approve the scope:

```yaml title=".yarnrc.yml"
npmPreapprovedPackages:
- "@backstage/*"
- "@openchoreo/*"
```

:::

In **CI**, GitHub Actions can use the auto-issued `GITHUB_TOKEN` instead of a PAT, provided the workflow has `permissions: { packages: read }` and the running repo is in (or a fork of) an org the package is published from.

---
Expand Down Expand Up @@ -586,7 +634,9 @@ openchoreo:
permission:
enabled: true # required for the OpenChoreo permission policy to run

# The catalog must accept Domain entities from the OpenChoreo provider.
# Only needed for static `catalog.locations` you add yourself. Entities emitted by
# an EntityProvider — which is how the OpenChoreo sync works — bypass catalog.rules
# entirely, so this block does not gate the OpenChoreo entities.
Comment on lines +637 to +639

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For Backstage v1.51.0, do entities submitted through EntityProviderConnection.applyMutationpass throughcatalog.rules? Cite the Backstage v1.51.0 source code or official documentation.

💡 Result:

In Backstage v1.51.0, entities submitted through EntityProviderConnection.applyMutation are not subject to catalog.rules validation [1][2][3]. Catalog rules are primarily designed to control the ingestion of entities originating from locations (such as those defined in catalog.locations) [1][4][2]. They are enforced by the DefaultCatalogProcessingOrchestrator within the processing pipeline [3], specifically applying to entities that have an associated location spec [1][3]. Entities submitted directly via an EntityProvider through applyMutation bypass this location-based rule enforcement [1]. As outlined in the official Backstage documentation, EntityProviders provide entities directly to the catalog engine [5][6]. While these entities undergo standard catalog processing steps (such as validation by EntityPolicies and processors), they do not trigger the catalog.rules check that applies to file-based or URL-based location ingestion [1][4][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
for f in \
  docs/platform-engineer-guide/backstage-plugins/installing-into-existing-backstage.mdx \
  docs/platform-engineer-guide/backstage-plugins/troubleshooting.mdx \
  docs/platform-engineer-guide/backstage-plugins/catalog-sync.mdx
do
  echo "=== $f ==="
  wc -l "$f"
  case "$f" in
    *installing-into-existing-backstage.mdx) sed -n '620,650p' "$f" ;;
    *troubleshooting.mdx) sed -n '265,295p' "$f" ;;
    *catalog-sync.mdx) sed -n '25,55p' "$f" ;;
  esac
done

echo "=== references to catalog.rules and EntityProvider ==="
rg -n -C 3 'catalog\.rules|EntityProvider|applyMutation' docs/platform-engineer-guide/backstage-plugins

Repository: openchoreo/openchoreo.github.io

Length of output: 8990


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
import json
import urllib.request

base = "https://raw.githubusercontent.com/backstage/backstage/v1.51.0/"
paths = [
    "plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts",
    "plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts",
    "plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts",
]
for path in paths:
    url = base + path
    print(f"=== {url} ===")
    try:
        with urllib.request.urlopen(url, timeout=20) as response:
            text = response.read().decode()
    except Exception as exc:
        print(f"FETCH_ERROR: {exc}")
        continue
    lines = text.splitlines()
    for i, line in enumerate(lines, 1):
        if any(term in line for term in ("rules", "applyMutation", "location")):
            lo, hi = max(1, i - 5), min(len(lines), i + 8)
            print("\n".join(f"{n}: {lines[n-1]}" for n in range(lo, hi + 1)))
            print("---")
PY

Repository: openchoreo/openchoreo.github.io

Length of output: 1037


🏁 Script executed:

#!/bin/bash
set -eu
base='https://raw.githubusercontent.com/backstage/backstage/v1.51.0'
for path in \
  plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts \
  plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts \
  plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts \
  plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.test.ts
do
  url="$base/$path"
  echo "=== $url ==="
  body="$(curl -ksSfL "$url" || true)"
  if [ -z "$body" ]; then
    echo "FETCH_ERROR_OR_EMPTY"
    continue
  fi
  printf '%s\n' "$body" | nl -ba | grep -E -C 8 'catalog\.rules|rules|applyMutation|EntityProvider|location' || true
done

Repository: openchoreo/openchoreo.github.io

Length of output: 889


🏁 Script executed:

#!/bin/bash
set -eu
base='https://raw.githubusercontent.com/backstage/backstage/v1.51.0'
for path in \
  plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts \
  plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts \
  plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts \
  plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.test.ts
do
  url="$base/$path"
  echo "=== $url ==="
  curl -ksSfL "$url" | grep -E -C 8 'catalog\.rules|rules|applyMutation|EntityProvider|location' || true
done

Repository: openchoreo/openchoreo.github.io

Length of output: 8904


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
import json, ssl, urllib.request

ctx = ssl._create_unverified_context()
url = 'https://api.github.com/repos/backstage/backstage/git/trees/v1.51.0?recursive=1'
req = urllib.request.Request(url, headers={'User-Agent': 'review-inspection'})
with urllib.request.urlopen(req, context=ctx, timeout=30) as response:
    tree = json.load(response)['tree']
for item in tree:
    path = item.get('path', '')
    if 'catalog-backend' in path and (
        'EntityProvider' in path or 'entityProvider' in path or
        'EntitiesCatalog' in path or 'CatalogService' in path or
        'rules' in path.lower()
    ):
        print(path)
PY

Repository: openchoreo/openchoreo.github.io

Length of output: 965


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
import ssl, urllib.request

ctx = ssl._create_unverified_context()
base = 'https://raw.githubusercontent.com/backstage/backstage/v1.51.0/'
paths = [
    'plugins/catalog-backend/src/service/DefaultCatalogService.ts',
    'plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts',
    'plugins/catalog-backend/src/providers/EntityProviderConnection.ts',
    'plugins/catalog-backend/src/providers/EntityProviderManager.ts',
]
for path in paths:
    url = base + path
    print(f'=== {url} ===')
    try:
        req = urllib.request.Request(url, headers={'User-Agent': 'review-inspection'})
        with urllib.request.urlopen(req, context=ctx, timeout=20) as response:
            lines = response.read().decode().splitlines()
    except Exception as exc:
        print(f'FETCH_ERROR: {exc}')
        continue
    for i, line in enumerate(lines, 1):
        if any(term in line for term in ('applyMutation', 'rulesEnforcer', 'deferredEntities', 'locationKey', 'originLocation')):
            lo, hi = max(1, i - 12), min(len(lines), i + 18)
            print('\n'.join(f'{n}: {lines[n-1]}' for n in range(lo, hi + 1)))
            print('---')
PY

Repository: openchoreo/openchoreo.github.io

Length of output: 10922


🏁 Script executed:

#!/bin/bash
set -eu
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
curl -ksSfL 'https://codeload.github.com/backstage/backstage/tar.gz/refs/tags/v1.51.0' -o "$tmp"
tar -tzf "$tmp" |
  grep -E 'catalog-backend/.+(EntityProvider|CatalogService|ProcessingManager|Database|rules)' |
  head -80

Repository: openchoreo/openchoreo.github.io

Length of output: 2115


🏁 Script executed:

#!/bin/bash
set -eu
tmp="$(mktemp)"
dir="$(mktemp -d)"
trap 'rm -f "$tmp"; rm -rf "$dir"' EXIT
curl -ksSfL 'https://codeload.github.com/backstage/backstage/tar.gz/refs/tags/v1.51.0' -o "$tmp"
tar -xzf "$tmp" -C "$dir"
root="$(find "$dir" -maxdepth 1 -type d -name 'backstage-*' -print -quit)"
grep -R -n -E -C 8 'applyMutation|rulesEnforcer|deferredEntities|locationKey' \
  "$root/plugins/catalog-backend" \
  --include='*.ts' --include='*.tsx' | head -300

Repository: openchoreo/openchoreo.github.io

Length of output: 38424


🏁 Script executed:

#!/bin/bash
set -eu
tmp="$(mktemp)"
dir="$(mktemp -d)"
trap 'rm -f "$tmp"; rm -rf "$dir"' EXIT
curl -ksSfL 'https://codeload.github.com/backstage/backstage/tar.gz/refs/tags/v1.51.0' -o "$tmp"
tar -xzf "$tmp" -C "$dir"
root="$(find "$dir" -maxdepth 1 -type d -name 'backstage-*' -print -quit)"

echo '=== applyMutation and provider connection ==='
grep -R -n -E -C 15 'applyMutation|EntityProviderConnection|connectEntityProviders' \
  "$root/plugins/catalog-backend" "$root/packages/catalog-node" \
  --include='*.ts' --include='*.tsx' | head -500

echo '=== relevant catalog service files ==='
find "$root/plugins/catalog-backend/src" -type f \
  \( -iname '*catalog*' -o -iname '*provider*' -o -iname '*refresh*' \) \
  -print | sort

Repository: openchoreo/openchoreo.github.io

Length of output: 50387


Remove the catalog.rules.allow requirement for EntityProvider entities.

Backstage 1.51.0 does not apply catalog.rules to entities submitted through EntityProviderConnection.applyMutation. Update catalog-sync.mdx and its troubleshooting guidance. Keep installing-into-existing-backstage.mdx and troubleshooting.mdx consistent. Retain catalog.rules guidance only for static catalog.locations.

📍 Affects 2 files
  • docs/platform-engineer-guide/backstage-plugins/installing-into-existing-backstage.mdx#L637-L639 (this comment)
  • docs/platform-engineer-guide/backstage-plugins/troubleshooting.mdx#L279-L282
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs/platform-engineer-guide/backstage-plugins/installing-into-existing-backstage.mdx`
around lines 637 - 639, Update the catalog rules guidance in catalog-sync.mdx,
installing-into-existing-backstage.mdx, and troubleshooting.mdx so
catalog.rules.allow is not required for EntityProvider entities submitted
through EntityProviderConnection.applyMutation. Retain the requirement only for
static catalog.locations, and keep the installation and troubleshooting
explanations consistent across all three files.

catalog:
rules:
- allow: [Component, System, Domain, API, Resource, Location, Group, User]
Expand Down Expand Up @@ -633,6 +683,16 @@ Expected:
5. Click into any project (`kind=system`) → **CELL DIAGRAM** and **DEFINITION** tabs are present and render real data.
6. Click into any component → **DEPLOY** and **DEFINITION** tabs are present and render real data.

:::warning Complete step 3 before judging steps 4–6

With `permission.enabled: true`, the catalog looks **empty** until OpenChoreo sign-in has completed. This is expected: the OpenChoreo permission policy authorizes catalog reads against the signed-in user's IDP token, and a guest session (or any service credential) has no OpenChoreo identity to evaluate, so the OpenChoreo entities are filtered out of every read.

What makes this easy to misread is that nothing reports it. There is no error and no warning, the provider still logs `Successfully processed N entities`, and the provider's `Template` entities are **not** filtered — so the Scaffolder fills with OpenChoreo templates while the catalog looks empty.

To confirm the sync itself is healthy, set `permission.enabled: false`, restart, and the full entity set appears. Set it back to `true` afterwards.

:::

If you get a 401 on tab data fetches, your `customAppModule`'s `ApiBlueprint` for `fetchApiRef` isn't being registered — check that `customAppModule` is in the `features: [...]` array in `App.tsx`. If you get "No permissions" on Deploy, same check for the `permissionApiRef` `ApiBlueprint`. See [Troubleshooting](./troubleshooting.mdx) for the full failure-mode index.

---
Expand Down Expand Up @@ -798,7 +858,18 @@ The plugin contributes the standalone `/workflows` page as a `PageBlueprint` —

Optionally add a sidebar entry in `packages/app/src/modules/nav/Sidebar.tsx` so users can navigate to it.

### 7.4 Verify
### 7.4 Configure

This pack shares the same feature flag as the CI/Build tab in [Section 6](#6-add-cibuild-tab-optional). If you installed Section 6 it is already set; if you are installing Section 7 on its own, add it:

```yaml
openchoreo:
features:
workflows:
enabled: true
```

### 7.5 Verify

Restart `yarn start`. Navigate to `http://localhost:3000/workflows` → see the org-level workflow list.

Expand Down
53 changes: 45 additions & 8 deletions docs/platform-engineer-guide/backstage-plugins/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ YN0090: @openchoreo/backstage-plugin@npm:1.2.0 is younger than the
configured minimum age (3d)
```

**Cause:** the NFS `create-app` scaffold's `.yarnrc.yml` sets `npmMinimalAgeGate: 3d` to protect against supply-chain attacks. Fresh `@openchoreo/*` prereleases sometimes land within that window.
**Cause:** `create-app` scaffolds newer than Backstage 1.51.0 set `npmMinimalAgeGate: 3d` in `.yarnrc.yml` to protect against supply-chain attacks. Fresh `@openchoreo/*` prereleases sometimes land within that window. (The Backstage 1.51.0 scaffold does not set this key at all, so you will not see this there.)

**Fix:** add `@openchoreo/*` to the pre-approved list in `.yarnrc.yml`:

Expand All @@ -81,6 +81,32 @@ npmPreapprovedPackages:
- "@openchoreo/*"
```

## Every yarn command fails: `Environment variable not found (GITHUB_PACKAGES_TOKEN)`

```
Usage Error: Environment variable not found (GITHUB_PACKAGES_TOKEN) in /path/to/.yarnrc.yml
```

**Cause:** `.yarnrc.yml` references the token as a bare `"${GITHUB_PACKAGES_TOKEN}"`. Yarn expands `.yarnrc.yml` variables on **every** invocation, not just `yarn install` — so `yarn tsc`, `yarn start` and `yarn lint` all abort whenever the variable is not exported.

**Fix:** add the `:-` empty-default suffix:

```yaml title=".yarnrc.yml"
npmScopes:
openchoreo:
npmRegistryServer: "https://npm.pkg.github.com"
npmAlwaysAuth: true
npmAuthToken: "${GITHUB_PACKAGES_TOKEN:-}"
```

Day-to-day commands then run unauthenticated, and only install/fetch needs the real token exported.

## `403 does not match expected scopes` from `npm.pkg.github.com`

**Cause:** using a `gh` CLI token. GitHub Packages rejects it regardless of the CLI's own scopes.

**Fix:** create a [classic Personal Access Token](https://github.com/settings/tokens/new) with the `read:packages` scope and export that as `GITHUB_PACKAGES_TOKEN`.

## Missing `alpha.core.metrics` service ref

```
Expand Down Expand Up @@ -235,15 +261,26 @@ lsof -i :7007 -i :3000
kill <pid>
```

## Catalog provider runs but no entities show up
## Catalog provider logs `Successfully processed N entities` but the catalog is empty

Check `catalog.rules.allow` includes `Domain` and any other custom kinds the OpenChoreo provider produces. The catalog silently drops disallowed kinds.
Almost always: **you are not signed in to OpenChoreo yet.**

```yaml
catalog:
rules:
- allow: [Component, System, Domain, API, Resource, Location, Group, User]
```
With `permission.enabled: true`, the OpenChoreo permission policy authorizes catalog reads against the signed-in user's IDP token. A guest session — or any service credential — has no OpenChoreo identity for the authorization service to evaluate, so every OpenChoreo entity is filtered out of the response. Sign in via **Sign in using OpenChoreo** and they appear.

Nothing reports this. There is no error, no warning, the provider still logs a successful run, and debug logging shows the entities being stitched. Two details make it more confusing:

- The provider's `Template` entities are **not** filtered, so the Scaffolder fills with OpenChoreo templates while the catalog looks empty. The integration looks healthy.
- The frontend shows the scaffold's own example entities (`example-website`, `examples`), because those come from static `catalog.locations` rather than from OpenChoreo.

To confirm the sync itself is fine, temporarily set `permission.enabled: false` and restart. The full entity set — components, systems, domains, environments and the `Cluster*` kinds — appears immediately. Set it back to `true` afterwards.

If you are signed in and entities are still missing, check that `openchoreo.features.authz.enabled` matches how the cluster was deployed; see [the cluster-mirroring warning](./installing-into-existing-backstage.mdx#configure-app-config).

:::note `catalog.rules` is not the cause

An earlier version of this page suggested adding kinds to `catalog.rules.allow`. That does not affect the OpenChoreo sync: entities emitted by an **EntityProvider bypass `catalog.rules` entirely**, which only governs static `catalog.locations`. You can verify this — the provider's `Template` entities are admitted even when `Template` is absent from the allow-list.

:::

## `NotificationsSidebarItem` crashes the app at boot

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ The OpenChoreo plugin set is tested against a specific Backstage release line. I

## Tested combination

| Component | Version |
| ---------------------- | ------------ |
| Backstage release line | **1.51.0** |
| Node.js | 20.x or 22.x |
| Yarn | 4.13.x |
| `@backstage/cli` | 0.36.x |
| OpenChoreo plugin set | `1.2.x` |
| Component | Version |
| ----------------------- | ------------ |
| Backstage release line | **1.51.0** |
| `@backstage/create-app` | 0.8.3 |
| Node.js | 22.x or 24.x |
| Yarn | 4.4.1 |
| `@backstage/cli` | 0.36.x |
| OpenChoreo plugin set | `1.2.x` |

`@backstage/create-app@0.8.3` is the scaffolder release that produces Backstage `1.51.0`, and the Yarn version listed is the one that scaffold ships in `.yarn/releases/`. Node 20 is **not** supported — the scaffold declares `"engines": { "node": "22 || 24" }`.

If you scaffold at a newer Backstage release and use `versions:bump --release 1.51.0` to come down, your Yarn version will be whatever that newer scaffold shipped (4.13.x at time of writing) rather than 4.4.1. Both work.

## Required `resolutions`

Expand Down
Loading