Skip to content

fix(showcase): resolve self-pointing connector baseUrl from the environment instead of a hard-wired 127.0.0.1:3000 - #7621

Merged
os-help merged 2 commits into
mainfrom
claude/issue-7538-showcase-connector-baseurl
Aug 11, 2026
Merged

fix(showcase): resolve self-pointing connector baseUrl from the environment instead of a hard-wired 127.0.0.1:3000#7621
os-help merged 2 commits into
mainfrom
claude/issue-7538-showcase-connector-baseurl

Conversation

@os-help

@os-help os-help commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Fixes #7538

What was wrong

StatusApiConnector and StatusOpenApiConnector carried the literal http://127.0.0.1:3000 in providerConfig.baseUrl (examples/app-showcase/src/system/connectors/index.ts:57 / :89). Any instance not listening on 3000 — CI, QA, any dev boot on an isolated port — could not self-ping, and every flow dispatching through those connectors failed with fetch failed. That symptom is indistinguishable from a sandbox egress block, which is what made it expensive to diagnose.

The premise held on origin/main: both literals were present exactly as triage reported, plus status-openapi.json:8's servers URL.

The measured question: can connector metadata read env?

The old comment at :55 asserted "metadata files don't read env". That is false, and the PR removes it. src/system/connectors/index.ts is an ordinary Node module imported by objectstack.config.ts (line 37), which itself already reads process.env at line 135 — same module graph, same process, same evaluation. Measured end to end below.

There is a real timing caveat, now documented in the new module rather than left as folklore: the read happens in whichever process loads objectstack.config.ts. On the os dev / os serve path (serve loads the config when present) that is the serving process, so the value follows the live environment. On the artifact-only path (os build once, os start --artifact) the connector metadata is serialized into dist/objectstack.json, so the value is frozen at build time. The plugin in plugins: is code and cannot be serialized at all, so it only exists on the config-load path.

Shape chosen: one shared resolver, used by both self-URL sources

New examples/app-showcase/src/system/self-url.ts exports resolveShowcaseSelfUrl(), and both self-URL sources now call it — the declarative connector instances and the ConnectorRestPlugin at objectstack.config.ts:135, which previously inlined its own process.env.SHOWCASE_SELF_URL ?? '...'. That is what keeps the two from diverging, which was the point of triage's bonus lead.

Resolution order, most explicit first:

  1. SHOWCASE_SELF_URL — full base URL; the escape hatch for anything a port cannot express (different host, https, proxy prefix). Kept primary because the config already documented it.
  2. OS_PORT, then its deprecated alias PORTthe same names, in the same order, that the CLI itself reads to pick the listen port (packages/cli/src/commands/serve.ts:204: readEnvWithDeprecation('OS_PORT', 'PORT') ?? '3000'). Following the CLI's own inputs is what makes the isolated-port boot in the issue's repro self-ping correctly with no extra configuration.
  3. http://127.0.0.1:3000 — the historical literal, unchanged, so a plain pnpm dev behaves exactly as before.

status-openapi.json keeps its literal servers[0].url: a static document cannot follow a bound port, and createOpenApiConnector resolves config.baseUrl ?? document.servers[0].url (packages/connectors/connector-openapi/src/openapi-connector.ts:152), so the connector's env-resolved baseUrl always wins. Its info.description now says so instead of leaving the stale-looking literal unexplained.

The showcase deliberately omits @types/node, so the new module and test each declare a module-scoped ambient process — the same idiom (and same stated reason) as objectstack.config.ts:55, rather than widening the shared test/node-shim.d.ts.

Verification

Unit — new examples/app-showcase/test/connector-self-url.test.ts (12 cases: precedence rungs, plus each connector under a non-3000 port, an explicit URL, and an empty environment).

Test Files  19 passed (19)
      Tests  183 passed (183)

tsc --noEmit green.

Reverse verification (predicted direction: RED on revert). Restoring the two literals via git checkout origin/main -- .../connectors/index.ts:

 × StatusApiConnector resolves against a non-3000 port
 × StatusOpenApiConnector resolves against a non-3000 port
 × both instances honour an explicit SHOWCASE_SELF_URL
 × no connector carries a hard-wired self URL any more
AssertionError: expected 'http://127.0.0.1:3000' to be 'http://127.0.0.1:4711'
 Tests  4 failed | 8 passed (12)

The four environment-following cases go red while the "still defaults to 3000" cases stay green — a literal is by construction still correct in the default case, so default-case assertions alone can never detect this bug. Both halves are required.

End-to-end through the real CLI compile (node node_modules/@objectstack/cli/bin/run.js compile), reading the compiled artifact's connector metadata:

OS_PORT=4711 → [["showcase_status_api","http://127.0.0.1:4711"],["showcase_status_openapi","http://127.0.0.1:4711"]]
no env       → [["showcase_status_api","http://127.0.0.1:3000"],["showcase_status_openapi","http://127.0.0.1:3000"]]

Live self-ping — a throwaway probe script (kept out of the repo) boots an HTTP server on 4711 and dispatches through the real createRestConnector / createOpenApiConnector built from the compiled providerConfig:

compiled baseUrl (rest)   : http://127.0.0.1:4711
compiled baseUrl (openapi): http://127.0.0.1:4711
rest dispatch   : {"status":"ok","servedOnPort":4711}
openapi dispatch: {"status":"ok","servedOnPort":4711}
server hits     : ["GET /api/v1/health","GET /api/v1/health"]

Counterfactual, same server on 4711 with the pre-fix literal — the issue's exact symptom:

compiled baseUrl (rest): http://127.0.0.1:3000
TypeError: fetch failed
  [cause]: Error: connect ECONNREFUSED 127.0.0.1:3000

That is the same before/after causality the QA run established with its TCP forwarder, obtained without one.

showcase-smoke.yml does NOT cover this. It is a Playwright console nav-render smoke, non-blocking, manual + nightly only, and examples/app-showcase/playwright.config.ts hard-codes const PORT = 3000 — so it exercises exactly the one port at which the bug is invisible, and it drives no connector dispatch.

node scripts/check-nul-bytes.mjs: OK. origin/main merged before opening (batch-mate #7542 works in src/automation/flows/, untouched here).

Changeset

None — examples/app-showcase is "private": true and publishes nothing, so no user-visible package releases. This PR takes the skip-changeset label route.


Generated by Claude Code

@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 11, 2026 9:09am

Request Review

@os-help os-help added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/m labels Aug 11, 2026 — with Claude
@os-help
os-help marked this pull request as ready for review August 11, 2026 09:40
@os-help
os-help added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit fe49d88 Aug 11, 2026
31 checks passed
@os-help
os-help deleted the claude/issue-7538-showcase-connector-baseurl branch August 11, 2026 09:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants