Repository navigation
Showcase REST connectors hard-wire baseUrl to http://127.0.0.1:3000, so self-ping flows fail fetch failed on any isolated instance — a port mismatch, not an egress block #7538
Description
Activity
Findings triage — resolved the filing-time
pm:queue+findingdual state topm:queue(findingremoved).- Grade rationale: concrete fixture defect with a causal repro (TCP-forwarder before/after isolates the address as the whole problem), named literals, and a stated preferred fix. Dispatchable as filed.
- Premise verified on
origin/main@744b8f5:examples/app-showcase/src/system/connectors/index.ts:57and:89still carry the literalbaseUrl: 'http://127.0.0.1:3000'(plusstatus-openapi.json:8'sserversURL). Bonus for the claiming seat:examples/app-showcase/objectstack.config.ts:135already doesprocess.env.SHOWCASE_SELF_URL ?? 'http://127.0.0.1:3000'— the env-indirection pattern resolution 1 wants exists in-repo, one file away; consider reusing the same variable so the two self-URL sources cannot diverge. - Routing check: connector fixtures exercise
packages/connectors/*surface ⇒domain:servicesper the domain table. Kept. - Dedup: Showcase flow comments overstate behaviour:
TaskCompletedRestPingFlow/ShowcaseDeclarativeConnectorPingFlowclaim the connector response is captured on the run, but neither declares anisOutputvariable #7542 (isOutput authoring gap) is the companion from the same QA run — different files, different fix. No overlap.
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
Claim: PM loop, services lane, wave 2 round 3
Session:session_015fkdTyGmMD5s8ZtEifvuGy
Branch:claude/issue-7538-showcase-connector-baseurl
Worktree:objectstack-issue-7538
Domain:domain:services
File surface:examples/app-showcase/src/system/connectors/index.ts(:57/:89baseUrl literals),examples/app-showcase/src/system/connectors/status-openapi.json(:8servers URL), and — if the env-indirection route is taken — a read of the pattern atexamples/app-showcase/objectstack.config.ts:135(SHOWCASE_SELF_URL). Stop on breach.
Container & model: S-grade fixture repair with triage-verified premise and a named preferred fix,mode:subagent,model: opus.
Serial constraints cleared:⚠️ batch-mate #7542 is in the SAME example app (examples/app-showcase) — fix sites are disjoint per triage's own dedup (src/system/connectors/*here vssrc/automation/flows/index.tsthere; "different files, different fix... no overlap"). Region-level declaration is this line; both devs mergeorigin/mainbefore opening their PR, and any conflict goes to the merge queue's arbitration, not manual ordering. No open PR or other in-flight claim touchesexamples/app-showcase.
Generated by Claude Code
ACCEPT — PR #7621, reviewed against GitHub rather than the report's own claims.
Verified in the diff (5 files, all inside the declared surface —
src/automation/flows/untouched, batch-mate #7542's face respected):- A false in-code assertion was measured and removed, not worked around. The old comment claimed "metadata files don't read env"; the dev proved it false (
connectors/index.tsis an ordinary Node module inobjectstack.config.ts's import graph, which already readsprocess.env) and documented the real caveat in its place: env is read wherever the config loads, soos dev/os servefollows the live environment while anos buildartifact freezes it at build time. Folklore replaced by a mechanism statement. - Triage's bonus lead executed exactly: one shared
resolveShowcaseSelfUrl()now feeds both self-URL sources — the declarative connector instances and theConnectorRestPluginline that previously inlined its own fallback — so the two cannot diverge again. - The resolution order follows the CLI's own inputs:
SHOWCASE_SELF_URL→OS_PORT→ deprecatedPORT→ the historical literal — the same names, same order, asserve.ts's port pick. An isolated-port boot self-pings with zero extra configuration, which is precisely the issue's repro scenario. status-openapi.jsonkept its literal for a measured reason: a static document cannot follow a bound port, andcreateOpenApiConnectorresolvesconfig.baseUrl ?? document.servers[0].url, so the env-resolved connector value always wins; the doc's description now says so.- Reverse verification makes the right structural point: reverting the literals reds exactly the four environment-following cases while the defaults-to-3000 cases stay green — a hard-wired literal is by construction correct in the default case, so default-case assertions alone can never catch this class. Both halves pinned.
- End-to-end through the real CLI compile (
OS_PORT=4711→ both connectors' compiledbaseUrlfollow; no env → 3000), plus a live self-ping probe whose counterfactual reproduces the issue's exactECONNREFUSED 127.0.0.1:3000. - Coverage-gap note worth keeping:
showcase-smoke.ymlruns Playwright at a hard-codedPORT = 3000and drives no connector dispatch — it exercises exactly the one port where this bug is invisible. Recorded here for whoever next trusts that smoke as connector coverage. - Suites: example-showcase 19 files / 183 tests green, typecheck clean.
- Changeset route:
examples/app-showcaseisprivate: true⇒skip-changesetlabel; applied at this review with read-back.
CI converging. Driving to landing: gate conclusions → ready → arm → queue verify.
Generated by Claude Code
- A false in-code assertion was measured and removed, not worked around. The old comment claimed "metadata files don't read env"; the dev proved it false (
{ "issue": 7538, "status": "done", "branch": "claude/issue-7538-showcase-connector-baseurl", "pr": "https://github.com/objectstack-ai/objectstack/pull/7621", "premise_still_valid": true, "summary": "Premise confirmed on origin/main: examples/app-showcase/src/system/connectors/index.ts:57 and :89 carried the literal baseUrl 'http://127.0.0.1:3000' (plus status-openapi.json:8's servers URL). Took the issue's preferred resolution 1 (env-overridable), in the shape that keeps ONE source of truth: a new examples/app-showcase/src/system/self-url.ts exports resolveShowcaseSelfUrl(), and BOTH self-URL sources now call it — the two declarative connector instances and the ConnectorRestPlugin at objectstack.config.ts:135, which previously inlined its own process.env read. Precedence: SHOWCASE_SELF_URL, else OS_PORT then its deprecated alias PORT (the same names in the same order the CLI itself reads to pick the listen port, serve.ts:204), else the unchanged 'http://127.0.0.1:3000' default. Falsified the PM hypothesis's premise-in-the-code: the old comment at :55 claimed 'metadata files don't read env' — that is FALSE (connectors/index.ts is an ordinary Node module imported by objectstack.config.ts:37, same process, same evaluation), so no config-layer injection was needed; the comment is removed. The real caveat, now documented rather than folklore: on the os dev/serve path serve loads the config so the value follows the live environment, while on the artifact-only path (os build → os start --artifact) connector metadata is serialized into dist/objectstack.json and the value is frozen at BUILD time. status-openapi.json keeps its literal servers[0].url because a static document cannot follow a bound port — createOpenApiConnector resolves config.baseUrl ?? document.servers[0].url (openapi-connector.ts:152), so the connector's env-resolved baseUrl always wins; its info.description now says so.", "tests": "pnpm --filter @objectstack/example-showcase^... build (dependency closure, exit 0) → typecheck: 'tsc --noEmit' clean → test: 'Test Files 19 passed (19) / Tests 183 passed (183)' post-merge with origin/main. New test examples/app-showcase/test/connector-self-url.test.ts (12 cases). REVERSE VERIFICATION, predicted direction RED-on-revert, confirmed: restoring the two literals via 'git checkout origin/main -- .../connectors/index.ts' gave 'Tests 4 failed | 8 passed (12)' — the 4 environment-following cases red ('AssertionError: expected http://127.0.0.1:3000 to be http://127.0.0.1:4711'), while every 'still defaults to 3000' case stayed GREEN, which is the point: a literal is by construction still correct in the default case, so default-case assertions alone can never detect this bug. END-TO-END through the real CLI compile: OS_PORT=4711 → [[showcase_status_api,http://127.0.0.1:4711],[showcase_status_openapi,http://127.0.0.1:4711]]; no env → both http://127.0.0.1:3000. LIVE SELF-PING via a throwaway probe (server on 4711, dispatch through the real createRestConnector/createOpenApiConnector built from the compiled providerConfig): both returned {status:ok,servedOnPort:4711}, server hits ['GET /api/v1/health','GET /api/v1/health']. COUNTERFACTUAL against the same 4711 server with the pre-fix literal reproduced the issue's exact symptom: 'TypeError: fetch failed / [cause]: Error: connect ECONNREFUSED 127.0.0.1:3000' — the same before/after causality the QA run needed a TCP forwarder for. Gate: node scripts/check-nul-bytes.mjs OK (7057 files); control-byte self-scan of all 5 touched files clean. CI at report time (card directs reporting at draft-PR time, not idle-polling): Check Changeset SUCCESS, Check PR Size / Auto Label / ADR gate / Console Pin Freshness / Build Core / Dogfood Verify CLI / 'No other open PR may claim the same issue' all success; ESLint and TypeScript Type Check still in_progress; Test Core (1-3/3), Dogfood Regression Gate (1-3/3) and Temporal Conformance still in_progress. No gate red at report time.", "open_questions": [], "out_of_scope_findings": [] }Notes the JSON cannot carry:
skip-changesetroute judged correct, and the label is already applied.examples/app-showcaseis"private": trueand publishes nothing, so no user-visible package releases and no.changeset/*.mdis owed. Labels read back after the bots settled:tests,skip-changeset— the label landed ahead of Check Changeset's re-read and that job concludedsuccess, so the PM does not need to apply it at review.showcase-smoke.ymldoes NOT cover this, as the card asked me to name. It is a Playwright console nav-render smoke — non-blocking,workflow_dispatch+ nightly only — andexamples/app-showcase/playwright.config.tshard-codesconst PORT = 3000. It therefore exercises exactly the one port at which this bug is invisible, and it drives no connector dispatch at all. I did not file this as a finding: it is a scoping fact about a deliberately non-blocking nightly smoke rather than a defect, and it is recorded in the PR body for whoever promotes that smoke to a gate. Flagging it here so the PM can grade it rather than me sitting on the judgement.- Batch-mate Showcase flow comments overstate behaviour:
TaskCompletedRestPingFlow/ShowcaseDeclarativeConnectorPingFlowclaim the connector response is captured on the run, but neither declares anisOutputvariable #7542 respected:src/automation/flows/untouched.origin/mainmerged before opening the PR (the merge brought showcase dashboard changes + a new showcase test; the full suite was re-run after it, hence 19 files / 183 tests).
Generated by Claude Code
Generated by Claude Code
- added a commit that references this issue
on Aug 17, 2026
Symptom
The showcase's REST connectors —
StatusApiandStatusOpenApi— declarebaseUrlas the literalhttp://127.0.0.1:3000in theirproviderConfig. The value is not env-overridable.Any instance that does not happen to be listening on port 3000 therefore cannot self-ping: every flow that dispatches through these connectors fails with
fetch failed. That is the normal case for CI, for QA runs, and for any local dev that boots on an isolated port — which is exactly the configuration these fixtures most need to work in.The important part: this is a port mismatch, not the sandbox egress block. Those two failure modes present identically (
fetch failed) and it is easy — and wrong — to write the failure off as "no outbound network in the sandbox" and stop. The run established causality: putting a throwaway TCP forwarder from 3000 to the instance's real port made the same flows succeed with no other change, giving clean before/after evidence that the destination address is the whole problem.The cost is not just a red run. It teaches every reader of the showcase that connector
baseUrlis a hard-coded literal, and it silently converts a fixture defect into an apparent platform/environment defect for anyone who hits it without the forwarder trick.Root cause
Fixture authoring, not the connector engine — dispatch itself is proven working in the same run (the MCP variant dispatches and captures output end to end, and an unregistered connector gives a named refusal rather than a silent no-op).
The
providerConfigforStatusApiandStatusOpenApicarriesbaseUrl: 'http://127.0.0.1:3000'as a literal, with no env indirection, so the value cannot follow the port the instance actually bound.Two acceptable resolutions:
baseUrlenv-overridable (read the instance's real base URL / port from the environment, defaulting to the current literal) so the self-ping fixtures work wherever the app boots. This is the better outcome — the flows become genuinely runnable and the fixture stops teaching a hard-coded address.knownGapsentry so the failure is declared rather than rediscovered. This is strictly the fallback: it makes the gap honest but leaves the clauses unrunnable.Reproduction
StatusApiorStatusOpenApi.fetch failed.127.0.0.1:3000and forwarding to the instance's real port, then re-trigger the same flow. It succeeds, unchanged.Re-check the literals:
Source
Extracted from the QA run #7516 (framework a86db17). Listed there as item 1 under "Fixture / authoring issues to fix"; it partially blocks
connector-dispatch-matrix.