Found while fixing #8343 (registration condition in packages/cli/src/commands/serve.ts). Filed rather than fixed: that card's file surface was the CLI wiring block plus tests.
packages/cloud-connection/README.md states the air-gapped contract correctly in prose:
OS_CLOUD_URL=off disables every remote call; air-gapped installs keep working via inline manifests handed to install-local.
The code example a few lines above it does the opposite — it puts MarketplaceInstallLocalPlugin inside the cloudUrl ? ternary, so OS_CLOUD_URL=off unmounts the very surface the prose promises keeps working, while RuntimeConfigPlugin({ installLocal: true }) sits outside the ternary and keeps advertising it:
const cloudUrl = resolveCloudUrl(); // OS_CLOUD_URL, 'off' disables
const plugins = [
...(cloudUrl ? [
new MarketplaceProxyPlugin({ controlPlaneUrl: cloudUrl }),
new MarketplaceInstallLocalPlugin({ controlPlaneUrl: cloudUrl }),
new CloudConnectionPlugin({ singleEnvironment: true, controlPlaneUrl: cloudUrl }),
] : []),
new RuntimeConfigPlugin({ controlPlaneUrl: '', singleEnvironment: true, installLocal: true }),
];
That is exactly the shape #8343 was filed against, including its second symptom (features.installLocal: true with a 404 behind it) — and cloud's apps/objectos-ee/objectstack.config.ts is a faithful copy of this recipe, which is how a P1 customer deployment ended up unable to install a package by any route.
Two further traps a corrected example should avoid teaching:
new MarketplaceInstallLocalPlugin({ controlPlaneUrl: cloudUrl }) where cloudUrl is the empty string does not mean "no cloud". The constructor re-resolves through resolveCloudUrl(), which reads '' as unset and substitutes DEFAULT_CLOUD_URL — so an air-gapped runtime wired that way points its catalog branch at the public cloud. One of the disable sentinels ('off') is the value that resolves to no cloud.
RuntimeConfigPlugin reports features.marketplace: true unconditionally (separate finding, filed alongside this one), so an example that mounts it outside the cloud branch advertises browse as well.
Suggested fix
Rewrite the example so install-local is outside the ternary and constructed with an explicit disable sentinel when there is no control plane, matching what serve.ts now does after #8343. Docs-only.
Backlink: #8343.
Generated by Claude Code
Found while fixing #8343 (registration condition in
packages/cli/src/commands/serve.ts). Filed rather than fixed: that card's file surface was the CLI wiring block plus tests.packages/cloud-connection/README.mdstates the air-gapped contract correctly in prose:The code example a few lines above it does the opposite — it puts
MarketplaceInstallLocalPlugininside thecloudUrl ?ternary, soOS_CLOUD_URL=offunmounts the very surface the prose promises keeps working, whileRuntimeConfigPlugin({ installLocal: true })sits outside the ternary and keeps advertising it:That is exactly the shape #8343 was filed against, including its second symptom (
features.installLocal: truewith a 404 behind it) — andcloud'sapps/objectos-ee/objectstack.config.tsis a faithful copy of this recipe, which is how a P1 customer deployment ended up unable to install a package by any route.Two further traps a corrected example should avoid teaching:
new MarketplaceInstallLocalPlugin({ controlPlaneUrl: cloudUrl })wherecloudUrlis the empty string does not mean "no cloud". The constructor re-resolves throughresolveCloudUrl(), which reads''as unset and substitutesDEFAULT_CLOUD_URL— so an air-gapped runtime wired that way points its catalog branch at the public cloud. One of the disable sentinels ('off') is the value that resolves to no cloud.RuntimeConfigPluginreportsfeatures.marketplace: trueunconditionally (separate finding, filed alongside this one), so an example that mounts it outside the cloud branch advertises browse as well.Suggested fix
Rewrite the example so install-local is outside the ternary and constructed with an explicit disable sentinel when there is no control plane, matching what
serve.tsnow does after #8343. Docs-only.Backlink: #8343.
Generated by Claude Code