Skip to content

fix(run): stop double-wrapping startup URLs when node.hostname is a URL - #2219

Open
dawsontoth wants to merge 7 commits into
mainfrom
claude/harper-double-wrapped-urls-a5cb92
Open

fix(run): stop double-wrapping startup URLs when node.hostname is a URL#2219
dawsontoth wants to merge 7 commits into
mainfrom
claude/harper-double-wrapped-urls-a5cb92

Conversation

@dawsontoth

@dawsontoth dawsontoth commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Fixes #2218. When node.hostname is configured as a full URL (e.g. http://localhost:9926), the Operations-API and REST lines of the startup banner composed URLs like http://http://localhost:9926:9925/ — the scheme was wrapped twice and the wrong port appended, because the banner assumed getThisNodeName() returns a bare host. A new nodeNameToDisplayHost() reduces the node name to a bare host (stripping any scheme/port, and bracketing a bare IPv6 literal) before each banner URL is composed; the three banner URL sites now use it. Result: http://localhost:9925/ (ops) and http://localhost:9926/ (REST).

For the human reviewer

  1. Scope — normalize at the banner, not in getThisNodeName(). Chosen because getThisNodeName() is the node's identity (cert CN, replication), so rewriting it is risky, while the reported bug is display-only. The alternative — normalizing at the source — would also fix hostnameToUrl(), which shares the same bare-host assumption and would produce a corrupt replication URL (ws://http://host:port) for a URL-valued node.hostname. That corruption is pre-existing and left out of scope here; the root-cause follow-up (validate/warn the bare-host node.hostname invariant in getThisNodeName(), which also covers hostnameToUrl()/replication and the security/keys.ts cert identity) is being handled as a separate change already in progress. Reversible either way.
  2. Hostname: banner line left verbatim. It still prints the raw configured node.hostname (the URL), deliberately — so an operator who configured a URL where a bare host is expected sees that, rather than having it silently masked. Only the composed URLs are normalized.

Verification

Route: unit tests (plain assert, no stubbing) at two levels — the pure nodeNameToDisplayHost helper across bare host / full URL / host:port / bracketed and bare IPv6, and an end-to-end test that drives the real exported startupLog() with a URL-valued node.hostname and asserts the composed Operations-API HTTP/HTTPS and REST banner lines are well-formed (no http://http:// double-wrap). Executed locally: unitTests/server/nodeName.test.js 15 passing and unitTests/bin/startupBanner.test.js 3 passing (18 together, no cross-suite leakage); npm run lint:required and prettier --check clean. The e2e test also fails as expected when the banner sites are reverted to getThisNodeName(), proving it catches the regression.

Review coverage

Authored by Opus 4.8. Cross-model review ran 6 delta rounds (one after each round of fixes). Codex gpt-5.6-sol gave independent, graded outside-family coverage every round; Gemini via agy (default model) ran on some rounds and failed others (agy flakiness); the Harper domain adjudicator and Cursor legs were auto-pruned as narrow low-risk deltas, so Codex's findings were not domain-adjudicated. Every substantive finding was fixed and re-verified: new Sinon usage (→ pure function + plain assert), a bare-IPv6 URL gap, missing getThisNodeHostname wrapper coverage, missing end-to-end startupLog coverage, and a test-isolation cleanup gap. One nit is left open by choice — a two-line "why" comment on nodeNameToDisplayHost, kept as durable rationale the house style permits. Receipt @ d7b5c46bb.

Human-Review-Need: 3 @ d7b5c46

The startup banner composed Operations-API and REST URLs as
'http://' + getThisNodeName() + ':' + port + '/'. When node.hostname is
configured as a full URL (e.g. http://localhost:9926), getThisNodeName()
returns that whole URL, so the banner wrapped the scheme twice and glued
on the wrong port: http://http://localhost:9926:9925/.

Add getThisNodeHostname(), which reduces the node name to a bare host
(stripping any scheme and port) for URL composition, and use it at the
three banner sites. Bare hosts pass through unchanged; IPv6 brackets are
preserved.

Fixes #2218

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request introduces a new helper function getThisNodeHostname in server/nodeName.ts to extract a bare hostname (without scheme or port) from the configured node name. This helper is then used in bin/run.ts to construct well-formed startup and REST URLs, preventing issues like double-wrapped schemes or incorrect ports when node.hostname is configured as a full URL. Comprehensive unit tests have also been added to verify various hostname formats, including bare hosts, full URLs, and IPv6 addresses. There are no review comments, and I have no feedback to provide.

@claude

claude Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Reviewed; no blockers found.

Replace string concatenation with template-literal interpolation for the
Operations-API HTTP/HTTPS URL lines. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread bin/run.ts
Comment on lines -324 to +327
logMsg += pad('') + 'http://' + getThisNodeName() + ':' + env.get(CONFIG_PARAMS.OPERATIONSAPI_NETWORK_PORT) + '/\n';
logMsg += `${pad('')}http://${getThisNodeHostname()}:${env.get(CONFIG_PARAMS.OPERATIONSAPI_NETWORK_PORT)}/\n`;
}
if (env.get(CONFIG_PARAMS.OPERATIONSAPI_NETWORK_SECUREPORT)) {
logMsg +=
'\n' +
pad('') +
'https://' +
getThisNodeName() +
':' +
env.get(CONFIG_PARAMS.OPERATIONSAPI_NETWORK_SECUREPORT) +
'/\n';
logMsg += `\n${pad('')}https://${getThisNodeHostname()}:${env.get(CONFIG_PARAMS.OPERATIONSAPI_NETWORK_SECUREPORT)}/\n`;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Swapping getThisNodeName for getThisNodeHostname is the only meaningful change, the rest is pure formatting.

- Extract nodeNameToDisplayHost(name) as a pure function so it can be unit
  tested with plain assert (no new sinon), per AGENTS.md test-style rule.
- Bracket a bare IPv6 literal (e.g. '::1') so composed banner URLs stay
  valid instead of emitting https://::1:9925/.
- Compress the added comments to a terse durable rationale.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread server/nodeName.ts Outdated
Comment thread server/nodeName.ts Outdated
Comment thread unitTests/server/nodeName.test.js Outdated
Address the round-2 cross-model review:
- Compress the helper's comments to a single durable contract, per the
  zero-new-comments default.
- Add a plain-assert test pinning that an unparseable node.hostname is
  returned unchanged rather than dropped (closes the surviving mutant the
  reviewer found: catch returning undefined).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread unitTests/server/nodeName.test.js
dawsontoth and others added 3 commits August 19, 2026 12:01
The round-2 refactor tested only the pure nodeNameToDisplayHost and stopped
exercising getThisNodeHostname — the wrapper bin/run.ts actually calls — so a
regression dropping normalization from the wrapper would pass. Add a
real-config test (via env.setProperty, no sinon) that a URL-valued
node.hostname is normalized to a bare host.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The pure nodeNameToDisplayHost helper and the getThisNodeHostname wrapper are
unit-tested, but nothing drove startupLog() itself, so a banner call site that
switched back to getThisNodeName() could recreate the double-wrapped URL while
those suites stayed green. Drive the real exported startupLog() with a
URL-valued node.hostname (via env.setProperty + console.log capture, no sinon)
and assert the Operations-API HTTP/HTTPS and REST URL lines are well-formed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address the round-5 review:
- startupBanner test snapshots and restores node.hostname and the secure ops
  port in cleanup instead of blanking them, so it cannot leak config into other
  suites sharing the mocha process.
- Trim the nodeNameToDisplayHost comment to the durable "why".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread unitTests/bin/startupBanner.test.js

@heskew heskew left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed; no blockers found. Clean display-host normalization and solid test coverage for startup banner URL formatting.


🤖 Posted by Antigravity on behalf of @heskew

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

URLs can log with multiple schema wrappings

3 participants