Skip to content

OS_CLOUD_URL=off unmounts the air-gapped install-local endpoint — the offline install path is gated on the cloud URL it is designed not to need #8343

Description

@baozhoutao

os package install ./dist/objectstack.json is documented as the air-gapped path — "the compiled artifact is read locally and sent inline — no catalog round-trip, works fully offline" (packages/cli/src/commands/package/install.ts). The plugin that serves it agrees: marketplace-install-local-plugin.ts:463 — "Bypass the cloud-fetch entirely; no OS_CLOUD_URL required".

But the plugin is only registered when a cloud URL exists (packages/cli/src/commands/serve.ts:1652):

constmarketplaceUrl=resolveCloudUrl();if(marketplaceUrl){awaitkernel.use(newMarketplaceProxyPlugin({controlPlaneUrl: marketplaceUrl}));awaitkernel.use(newMarketplaceInstallLocalPlugin({controlPlaneUrl: marketplaceUrl}));awaitkernel.use(newRuntimeConfigPlugin({controlPlaneUrl: '',singleEnvironment: true,installLocal: true}));}

With OS_CLOUD_URL=off — the value the self-hosted EE compose file documents as "完全自托管填 off" — resolveCloudUrl() is empty, the whole block is skipped, and the endpoint never mounts. So the deployment that most needs offline install is the one that cannot have it.

Measured

objectos-ee-deploy (image ghcr.io/objectstack-ai/objectos-ee@sha256:c93b4937…, tag 4.0.5-rc.1), OS_CLOUD_URL=off, container command node node_modules/@objectstack/cli/bin/run.js start (which forks serve):

GET /api/v1/marketplace/install-local → 404 {"error":"Not found"}
POST /api/v1/marketplace/install-local → 404 {"error":"Not found"}

There is no other package-install surface: the served OpenAPI document contains no path matching package|marketplace|install, and the container mounts only /data (the artifact is baked into the image at OS_ARTIFACT_PATH=/repo/cloud/apps/objectos-ee/dist/objectstack.json). OS_ARTIFACT_PATH takes a single artifact, not a directory or a list (packages/runtime/src/default-host.ts:49), so there is also no "drop another bundle in" workaround. The deployment simply cannot install a package by any route.

Second symptom: the Console is told the feature is live

GET /api/v1/runtime/config on the same deployment returns:

{"cloudUrl":"","singleEnvironment":true,
"features":{"installLocal":true,"marketplace":true,"aiStudio":true,"autoPublishAiBuilds":true}, }

installLocal: true with no endpoint behind it. Whatever supplies runtime-config in the EE image is not the RuntimeConfigPlugin from the skipped block, so the flag and the route now disagree — the Console will offer an install affordance that 404s.

Expected

Registration of MarketplaceInstallLocalPlugin should not depend on resolveCloudUrl(). Only the catalog branch needs a control plane, and it already degrades correctly on its own (marketplace-install-local-plugin.ts:494503 MARKETPLACE_UNAVAILABLE, "OS_CLOUD_URL not configured."). Mounting it unconditionally therefore costs nothing and restores the documented offline path; MarketplaceProxyPlugin (a pure cloud proxy) can stay behind the URL check.

And whichever component reports features.installLocal should report what is actually mounted.

Impact

This is the blocking issue for installing any first-party or customer application into a self-hosted EE deployment — the concrete case here was installing HotCRM (app.objectstack.hotcrm) into a running multi-org EE stack, for which no supported path currently exists.

Environment

objectos-ee-deploy (Caddy → app → postgres:16), NODE_ENV=production, OS_TENANCY_POSTURE=isolated, OS_MODE=standalone, OS_CLOUD_URL=off, on http://localhost:8080, observed 2026-08-13. Source references are the current framework main checkout.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions