Skip to content

Name the server instead of printing its URL (#391) - #412

Merged
bgmcmullen merged 2 commits into
masterfrom
name-server-not-url-391
Jul 28, 2026
Merged

Name the server instead of printing its URL (#391)#412
bgmcmullen merged 2 commits into
masterfrom
name-server-not-url-391

Conversation

@bgmcmullen

@bgmcmullen bgmcmullen commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Closes #391.

Problem

hyp remote login printed the destination as a bare URL, in two places:

forwarding logs to https://hypaware.hyperparam.app
...
  first sync to https://hypaware.hyperparam.app is 11:59pm tonight

Warp, iTerm2, VS Code's terminal and Ghostty all autolink any https:// run in output. There is no ANSI escape that suppresses this - OSC 8 exists to add an explicit hyperlink, not to opt out of heuristic detection. So the link in #391 wasn't a rendering quirk on the reporter's machine; it is what every modern terminal does with that line. The click lands on a service endpoint that returns {"error":"unknown_path"}.

That is worst in the privacy block, where an invitation to a dead page sits in the one message asking the user to trust where their data is going.

Change

Print the configured target name instead of the URL, and pair each destination surface with the command that maps that name back:

forwarding logs to the 'prod' server
  (run 'hyp remote list' to see its URL)

──────────────────────────────────────────────
  PRIVACY - review before first sync

  first sync to the 'prod' server is Jul 27, 2026, 11:59 PM
  and includes your backfilled history
  (run 'hyp remote list' to see that server's URL)

  to review what ships before then,
  open Claude or Codex and run the hypaware-privacy skill
──────────────────────────────────────────────

The name is whatever the target is called - hyperparam for the built-in in builtin_remotes.js, or prod, or whatever was passed to hyp remote add <name> <url>.

The lookup pointer is load-bearing, not decoration. A bare hyp remote login resolves its target from effectiveDefaultRemote, so the name shown can be one the user never typed, and nothing else this login prints recovers the URL: hyp status names no server, DURABLE_HINT points at hyp policy set, the privacy block points at the skill, and hyp remote status does not exist. Withholding the URL is a readability choice; withholding the way to see it would make a consent surface unauditable.

Each surface carries its own pointer because the forwarding line is stdout and the privacy block is stderr - redirect either and the other has to stand on its own.

  • src/core/cli/remote_commands.js:650-651 - the forwarding line and its pointer
  • firstSyncHoldMessage (remote_commands.js:211) - param centralUrlserverName, call site passes name, pointer line at :219

centralUrl is still computed and still used for enroll(); it just no longer reaches a printed string.

Not changed

  • hyp remote add and hyp remote list still show URLs. There the URL is the content, not a destination being offered.
  • remote_commands.js:513 (this machine is connected to <origin>) and its sibling error paths still print bare origins. There the origin is the fact the user must act on to decide which server to hyp leave, and those paths read origins back off sink config with no target name in scope, so naming them needs a reverse lookup - a separate change. The R1a scope note and the code comment both say so explicitly.

On the server-side fix

This takes #391's suggested fix 2, in its stronger form: rather than relabelling the URL API endpoint:, drop it. Labelling doesn't stop anyone clicking.

Making / a real landing page (suggested fix 1) is still worth doing and is the better end state - it would let the CLI print a URL that rewards the click, and it's the natural home for the hosted explorer in #392. That's a server-repo change; this PR is the client half that stops the dead click today. Both the code comment and R1a say to revisit if the root ever becomes browsable.

LLP

Adds R1a to LLP 0100 §requirements plus a matching entry in §annotations.

The first round of this PR argued no doc change was needed - R1 asks for the deadline, the backfill statement and the skill hint, so the destination's spelling looked like ours to choose. Review finding 4 rejected that: the rule was living only in code comments, which is how a durable constraint regresses silently. R1a now pins it normatively - name the server by its configured target name, never print its URL, and name the command that maps the name back - along with the reason (terminals autolink, the root is a service endpoint), the scope (success surfaces only; error paths still print origins), and the revisit condition. Both surfaces carry @ref LLP 0100#requirements [implements]: R1a.

Verification

  • npm test - 2744 passing, 1 skipped, 0 failing
  • npm run typecheck - clean

Test changes:

  • The five existing forwarding logs to assertions and the three first sync to assertions were updated for the new wording.
  • One regression test asserts the whole login output matches no https?:// at all, rather than pinning the two lines individually - so any future line that reintroduces a bare origin trips it.
  • The same test pins the pointer per stream (stdout and stderr separately), so dropping one of the two is a failure rather than a silent regression to a half-auditable surface.
  • Added a bare hyp remote login test driving the enrolling fork with no positional target and no query.default_remote: the effectiveDefaultRemote path the review noted was unpinned, and the case that makes the pointer load-bearing.

Note: node_modules on my machine was stale after the #406 dep bump (hyparquet missing, 139 spurious failures). npm install fixed it; package-lock.json is unchanged by this PR.

🤖 Generated with Claude Code

`hyp remote login` printed `forwarding logs to https://hypaware.hyperparam.app`,
and the first-sync privacy block printed the same origin. Terminals autolink
any bare `https://` run and there is no escape sequence that suppresses it, so
both reliably rendered as clickable links to a service endpoint that answers
`{"error":"unknown_path"}` in a browser.

Print the configured target name instead: `forwarding logs to the 'prod'
server`, `first sync to the 'prod' server is 11:59pm tonight`. The name is what
the user typed to reach the server, and `hyp remote list` stays the place that
maps a name back to a URL.

`hyp remote add` and `hyp remote list` still show URLs - there the URL is the
content, not a destination being offered.

LLP 0100 R1 needs no change: it requires the deadline, the backfill statement,
and the skill hint, and LLP 0100 #flow already writes the example as
`first sync to <server>`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bgmcmullen bgmcmullen added the neutral:review Delegate this PR to neutral for a review pass (approve or request changes; never merges) label Jul 27, 2026
@philcunliffe

Copy link
Copy Markdown
Contributor

Review round 1 - 8d64c99

Verdict: findings, no blockers. The PR is correct and well made, the diagnosis is right, and the fix is the right call. Four findings, none of which break anything; the first is the one worth acting on.

On the design question, plainly: the swap is right. A dead-link click inside a privacy notice actively damages the trust that notice exists to build, which is worse than opacity, and the name is genuinely resolvable. Your reading of the terminal behavior is also correct: there is no ANSI escape that opts out of heuristic autolinking, so this was never a rendering quirk on the reporter's machine.

Findings

1. NON-BLOCKING, and the one I would act on. The URL is no longer reachable from any command the output points at. remote_commands.js:632 and :207. The docstring justifies the swap with "hyp remote list is where name to URL lives", which is true, but nothing in the login output says so, and the commands the output does name cannot recover the destination: hyp status prints no URL at all (its first-sync line at commands/status.js:387 names no server), DURABLE_HINT points at hyp policy set, and the privacy block points at the hypaware-privacy skill. hyp remote status does not exist - the group is add | login | list | remove (core_commands.js:425-460). Only hyp remote list maps name to URL, and it is never mentioned.

Verified by running the real code path: a bare hyp remote login resolves the name from effectiveDefaultRemote(ctx.config) (:305), not from anything the user typed, so the output reads forwarding logs to the 'hyperparam' server for a name the user never typed. They are then told their backfilled history ships tonight, and no command they are directed to can tell them where it is going. The same holds when query.default_remote comes from a config layer rather than the user, and the wizard's join lane hits this path too.

So the PR completes only half the trade: it removed a verifiable fact without leaving a signposted way back to it, in the one message that exists to earn trust. One line after :632 would close it, e.g. (run 'hyp remote list' to see its URL), or surfacing the central URL in hyp status.

2. NON-BLOCKING. The same dead-click defect survives at three sibling sites in the same file: :502 (this machine is connected to ${connectedOrigins[0]}), :623 (connected to ${result.connectedElsewhere} during sign-in), and :684-687 (the replaced-identity note interpolating central_url). All three write a bare origin to stderr from hyp remote login - the same origin your comment notes returns unknown_path - and terminals autolink them identically. hyp leave does the same at commands/central.js:384,524,581. Failure scenario: a user enrolled to server A runs hyp remote login prod for server B, :502 fires, the terminal linkifies A's origin, and the click lands on unknown_path.

Defensible to leave: these are error and diagnostic paths where the origin is the load-bearing fact (you need it to decide whether to run hyp leave), unlike the privacy block where a label suffices. But the docstring at :626-631 states the rule as general, so a scope note or a follow-up issue would keep it honest rather than silently partial.

3. NON-BLOCKING, minor. The no-URL invariant is name-dependent. runRemoteAdd (:234-243) validates only the url argument, never the name, so hyp remote add https://hyp.internal https://hyp.internal/mcp is accepted and login then prints forwarding logs to the 'https://hyp.internal' server (verified by running it). A name containing a newline is likewise accepted and splits the rule-delimited privacy block across lines. Contrived, but the PR now interpolates an unvalidated, config-controlled string into a fixed-format trust notice. Rejecting URL-shaped or whitespace-bearing names in remote add would close it.

4. NON-BLOCKING, process. The rule lives only in code comments. The rationale sits in two block comments (:187-194, :626-631) citing #391, not in an LLP. The docstring's claim was checked against the doc and is honest: llp/0100-enrollment-privacy-review.spec.md §flow step 3 writes first sync to <server> is tonight at 11:59pm, a placeholder committing to neither spelling, so the living-docs rule did not strictly require an edit. But "the enrollment privacy block must never print a server URL" is exactly the kind of durable constraint that regresses silently, and a clause on LLP 0100 R1 would pin it.

Verified clean

Completeness within the privacy and first-sync path is complete. The only other first-sync surfaces name no server at all (status.js:387, wizard/index.js:305-311), so there is no missed site in a privacy block. That was the most serious version of this bug and it is not present.

No degenerate name produces undefined, the '' server from a reachable path, or a crash. name is always a validated key of readConfiguredRemotes (guarded at :478-483), an unconfigured name exits 2 before any message, and there is no ad-hoc URL login path. Verified by driving the real runRemoteLogin through the enrolling fork with 12 name variants: builtin default, layered default_remote, quotes in the name (the 'bob's-box' server), spaces, non-ASCII (prodüktion-日本), two configured targets, and a name/URL mismatch.

The signature change is safe (firstSyncHoldMessage has exactly one caller repo-wide, so no caller was left passing a URL). Quoting matches ten other sites in the same file. Conventions clean: no U+2014 on any added line, no semicolons, and @ref LLP 0100#requirements resolves.

The tests are load-bearing, not decorative, confirmed by mutation rather than by reading: reverting firstSyncHoldMessage(holdDeadline, name) to centralUrl fails 4 tests including the new #391 one, and reverting the forwarding logs to line fails 6. They cover both surfaces plus a whole-output doesNotMatch(/https?:\/\//) invariant.

Two small test notes: the "no printed URL" claim holds partly because login is stubbed, since in production oidc_login.js:69,71 routes the authorize URL to stderr (a live sign-in page, not a dead endpoint, so not a defect, but the assertion reads broader than it is). And the default-name path is not pinned through the enrolling fork, because the existing bare-login test returns early at :558 with a stubbed session carrying no gateway.

Verification run

npm test at the PR head: 2667 tests, 2658 pass, 8 fail. At origin/master: 2666 tests, 2657 pass, 8 fail. The failure count did not grow, all 8 are in test/core/leave-command.test.js, still the only failing file. npm run typecheck clean. node --test test/core/remote-login-command.test.js: 60/60 pass.


Requesting changes on finding 1 only: the login output should name a command that can recover the URL, since it is the one place the user is asked to trust a destination they can no longer see. Findings 2 through 4 are yours to weigh and may be better as follow-ups. The verdict is advisory: neutral does not ready or merge contributor PRs, so landing this is your call.

@philcunliffe philcunliffe added the neutral:changes-requested neutral reviewed an adopted PR and requests changes (non-binding; maintainer decides) label Jul 27, 2026

@philcunliffe philcunliffe 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.

Reviewed at 8d64c99. Full record above.

The diagnosis and the fix are both right. There is no ANSI escape that opts out of heuristic autolinking, so this was never a rendering quirk, and a dead-link click inside a privacy notice damages the trust that notice exists to build. Swapping the URL for the name is the correct call. Completeness within the privacy and first-sync path is verified complete, no degenerate name produces a crash or an empty label from any reachable path (12 variants driven through the real enrolling fork), and the tests are load-bearing rather than decorative, confirmed by mutation.

Requesting changes on one finding only. The PR removed a verifiable fact without leaving a signposted way back to it. The docstring justifies the swap with "hyp remote list is where name to URL lives", but nothing in the output says so, and the commands it does name cannot recover the destination: hyp status prints no URL, DURABLE_HINT points at hyp policy set, the privacy block points at the skill, and hyp remote status does not exist. On a bare hyp remote login the name comes from effectiveDefaultRemote(ctx.config), so the user is told a name they never typed, told their backfilled history ships tonight, and given no way to find out where.

One line after remote_commands.js:632 closes it, e.g. (run 'hyp remote list' to see its URL), or surface the central URL in hyp status.

Findings 2 to 4 in the record are yours to weigh and may be better as follow-ups: three sibling sites in the same file still print bare origins to stderr (defensible, since the origin is the load-bearing fact on an error path, but the docstring states the rule as general), remote add never validates the name so a URL-shaped name is interpolated into the trust notice, and the durable rule lives only in code comments rather than an LLP clause.

Advisory as always: neutral does not ready or merge contributor PRs.

Review finding 1: naming the server instead of its URL removed a verifiable
fact without signposting the way back to it. Nothing in the login output said
where to look, and none of the commands it does name can recover the URL - `hyp
status` names no server, DURABLE_HINT points at `hyp policy set`, the privacy
block points at the skill, and `hyp remote status` does not exist. A bare `hyp
remote login` resolves its target from `effectiveDefaultRemote`, so the user
could be shown a name they never typed, told their backfilled history ships
tonight, and given no way to find out where.

Both destination surfaces now carry the lookup. Each stream gets its own,
because the forwarding line is stdout and the privacy block is stderr, and
redirecting either must leave the other standing on its own:

  forwarding logs to the 'hyperparam' server
    (run 'hyp remote list' to see its URL)
  ...
    first sync to the 'hyperparam' server is Jul 27, 2026, 11:59 PM
    and includes your backfilled history
    (run 'hyp remote list' to see that server's URL)

Review finding 4: the rule lived only in code comments, which is how a durable
constraint regresses silently. Pinned as LLP 0100 R1a, with @refs on both
surfaces.

Review finding 2, partial: the forwarding-line comment stated the rule as
general when three sibling error paths still print bare origins. Scoped it to
the success surfaces and said why the error paths differ - there the origin is
the fact the user must act on to decide which server to `hyp leave`.

Tests: pin the pointer on both streams, and drive a bare `hyp remote login`
through the enrolling fork - the default-name path the review noted was
unpinned, and the case that makes the pointer load-bearing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bgmcmullen

Copy link
Copy Markdown
Contributor Author

Thanks - finding 1 is right, and it's the half of the trade I missed. Addressed in 0714482.

Finding 1 (blocking) - fixed

Verified each of your claims before changing anything: status.js:385 prints the first-sync line with no server named, hyp remote is add | login | list | remove with no status, and :305 does resolve a bare login's target from effectiveDefaultRemote. So the scenario is real - a name the user never typed, plus a deadline, plus no way to see the destination.

Both surfaces now carry the lookup:

forwarding logs to the 'hyperparam' server
  (run 'hyp remote list' to see its URL)

──────────────────────────────────────────────────────────────
  PRIVACY - review before first sync

  first sync to the 'hyperparam' server is Jul 27, 2026, 11:59 PM
  and includes your backfilled history
  (run 'hyp remote list' to see that server's URL)

  to review what ships before then,
  open Claude or Codex and run the hypaware-privacy skill
──────────────────────────────────────────────────────────────

Deliberately in both rather than once after :632 as you suggested: the forwarding line is stdout and the privacy block is stderr, so redirecting either strands the other. The block's own docstring already claims it is self-contained; a pointer that lives only on the other stream would make that false.

The framing I took from your finding, now in the code and the LLP: withholding the URL is a readability choice, withholding the way to see it makes a consent surface unauditable.

Finding 4 - done

Pinned as LLP 0100 R1a, with @refs on both surfaces and an entry in §refs. You were right that the docstring's claim was honest and no edit was strictly required, and equally right that "the privacy block must never print a server URL" is exactly the constraint that regresses silently.

R1a binds the success surfaces, requires the lookup pointer, states why the pointer is not optional (the effectiveDefaultRemote case), explicitly excludes the error paths, and says to revisit if the server root becomes browsable.

Finding 2 - scoped, not fixed

Took the scope-note option. The comment stated the rule as general while :502, :623, :684-687 and the three in commands/central.js still print bare origins; it now says the rule binds the success surfaces and why the error paths differ - there the origin is the fact you must act on to decide which server to hyp leave, and a label genuinely would not do. R1a carries the same exclusion.

Happy to file the follow-up if you'd still like those changed; my read is that they're correct as-is and the honesty gap was the comment, not the behavior.

Finding 3 - not taken here

Agreed it's real - runRemoteAdd validates the url argument and not the name, so a URL-shaped or newline-bearing name is interpolated into a fixed-format trust notice. But it's a behavior change to a different command with its own compatibility question (what to reject, and whether existing configs break), so it belongs in its own PR rather than riding this one. Will file it.

Tests

Added the two you'd expect: the pointer pinned on both streams separately, and a bare hyp remote login driven through the enrolling fork - the default-name path you noted wasn't covered, and the one that makes the pointer load-bearing instead of decorative. It asserts against effectiveDefaultRemote(ctx.config) rather than a hardcoded name.

Your note about the whole-output doesNotMatch(/https?:\/\//) reading broader than it is, because login is stubbed and oidc_login.js:69,71 writes a real authorize URL to stderr in production, is fair. Left as-is: that URL is a live sign-in page, so it's outside what the assertion is protecting, and narrowing it to specific lines would weaken the property I actually want (no new line reintroduces a bare origin).

Verification

npm test 2668 tests, 2667 pass, 0 fail, 1 skip. npm run typecheck clean.

One discrepancy worth flagging: I can't reproduce your 8 failures in test/core/leave-command.test.js - green here on both this branch and origin/master. Since your run showed the same 8 on master, it looks environmental rather than PR-related, but flagging in case it's real on CI and clean locally for me.

@philcunliffe

Copy link
Copy Markdown
Contributor

Review round 2 - 0714482

Verdict: findings, no blockers. Round 1's blocking finding is genuinely fixed.

The fix was verified by running the real code path rather than reading it, and the risk that mattered - a pointer to a command that shows nothing would be worse than no pointer - was checked directly.

Round 1 fix verification

# Item Result
1 BLOCKING - no signposted way back to the URL PASS
2 Sibling bare-origin sites PASS, deferred with documented rationale (see finding 2)
3 runRemoteAdd does not validate the name PASS, deferred to issue #414
4 Durable rule lived only in code comments PASS, normative R1a added

Finding 1, in detail. Both surfaces carry the pointer, and your stdout/stderr reasoning is factually correct rather than assumed: remote_commands.js:650-651 writes the forwarding line plus its pointer to stdout, :659 writes the privacy block carrying its pointer at :219 to stderr. A driver capturing the two streams separately confirmed each pointer lands on the claimed stream, so redirecting either genuinely leaves the other self-sufficient. Putting it in both rather than once was the right call.

hyp remote list really does surface the URL, including the hard case. readConfiguredRemotes (:861-874) seeds BUILTIN_REMOTES under the user's query.remotes, so on a bare login with an empty config it prints hyperparam https://hypaware.hyperparam.app token: stored - the exact name the privacy block just showed. Ran it.

Every path that prints these lines carries the pointer. Exercised: a named login (prod), a bare login with no remotes configured (resolving hyperparam through effectiveDefaultRemote, which was round 1's specific worry), a bare login with query.default_remote, and --no-daemon. All four print both pointers, and the --no-daemon and daemonCode !== 0 early returns sit at :663/:668, after the writes at :650-660, so neither can skip them. The wizard join lane goes through the identical code path (wizard/join.js:179 calls runRemoteLogin([], teed, {}) with a write-through tee). The already-enrolled re-login path correctly prints neither line, so the absence of a pointer there is right rather than a gap.

Regressions: none. Box formatting is intact (rule 62 chars, new pointer line 50, longest line is the pre-existing deadline line at 65, and the block has no side borders so nothing misaligns). The existing doesNotMatch(/https?:\/\//) invariant at remote-login-command.test.js:944 does cover the new lines, since it runs over out + err. No brittle whole-output equality assertions apply. Conventions clean: no U+2014 added anywhere in the delta (the em dashes present in both files are byte-identical at base ae63a02), no semicolons, no @typedef, no inline import() types. @ref LLP 0100#requirements [implements]: R1a at :206 and :649 resolve.

Master moved to dd3b438 (#408 and #407 landed) and this branch is behind. A trial merge is conflict-free, master's commits touch none of remote_commands.js, and the merged tree passes: 2745 tests, 8 failures, all the pre-existing leave-command.test.js ones.

Findings

1. NON-BLOCKING, but worth fixing before merge. The PR description is stale against the head and now contradicts what ships. The body was written for 8d64c99 and never updated for 0714482:

  • Its ## Change block shows the privacy block without either pointer line, which is the exact copy round 1 rejected. A maintainer reading the body to decide whether to merge sees the version that was blocked.
  • Its ## LLP section says verbatim "No doc change needed ... the destination's spelling is ours to choose." The head commit adds normative R1a at llp/0100-enrollment-privacy-review.spec.md:109-120 doing precisely the opposite: it pins the spelling and mandates the lookup command. I checked both texts directly. The body actively misdescribes the design decision that lands.
  • Line citations drifted (:626 is now :650, :493 is now :513), and ## Verification claims 2666 passing, 0 failing where the real numbers at this head are 2659 pass / 8 fail (the pre-existing failures, not this PR's fault, but the stated number is not reproducible).

Failure scenario: the squash commit message and the permanent PR record describe a design that is the opposite of the code and the LLP clause actually landing. This is a body edit, no code change.

2. NON-BLOCKING, carried forward. The residual bare-origin sites are scoped out honestly, but two of them fall outside the carve-out. The delta documents the boundary in two places (remote_commands.js:640-643 and the R1a clause at llp/0100:118-120), both saying the rule binds the success surfaces and that error paths still print origins, where the origin is the fact the user must act on. That is a good, honest scoping.

The gap: commands/central.js:384 (leaving <url>) and :524 (left <url> - this machine no longer forwards…) are hyp leave success surfaces, not error paths, so the documented carve-out does not cover them, and R1a is scoped to "the enrolling login's destination surfaces" so it does not reach hyp leave either. Since the body says Closes #391, and #391's scope really is narrowly the enrollment forwarding line, closing it is legitimate - but once this merges nothing tracks those two. A one-line follow-up issue closes the loop, the way #414 did for finding 3.

One nuance noted for completeness, not raised as a finding: for a user-added target, hyp remote list shows the configured query URL (https://hyp.internal/mcp) while forwarding goes to its origin (https://hyp.internal, :610). Since targetOrigin = originOf(entry.url) by construction the host is always identical and only a path suffix differs, so the user still learns the correct server, and for the built-in the two are byte-identical.

Verification run

node --test test/core/remote-login-command.test.js: 61 pass, 0 fail. npm test at this head: 2668 tests, 2659 pass, 8 fail. On the merged-with-master tree: 2745 tests, 2736 pass, 8 fail. npm run typecheck clean on both. The failure count did not grow and leave-command.test.js is still the only failing file, proved by running it at base ae63a02 in a throwaway worktree and getting the identical 3 pass / 8 fail with the same test names.

@philcunliffe philcunliffe added neutral:approved neutral reviewed this and holds it for a maintainer merge (own or adopted PR; LLP 0025/0030) and removed neutral:changes-requested neutral reviewed an adopted PR and requests changes (non-binding; maintainer decides) labels Jul 28, 2026
@philcunliffe

Copy link
Copy Markdown
Contributor

Round 2 reviewed at 0714482. Full record above.

Approving. The blocking finding is genuinely fixed, verified by running the real code path across four enrolling scenarios plus the wizard join lane, not by reading it. Your stdout/stderr reasoning checks out, so putting the pointer on both surfaces rather than once was the right call. The risk I was most worried about (a pointer to a command that shows nothing) does not materialize: readConfiguredRemotes seeds the builtins under the user's config, so a bare login with no remotes still resolves hyperparam to a real URL in hyp remote list. Nice touch adding R1a rather than leaving the rule in a comment, and filing #414 rather than dropping finding 3.

One thing to fix before you merge, which is a body edit and not code: the PR description is stale against the head and now contradicts what ships. Its ## Change block still shows the privacy block without either pointer line, which is the copy round 1 rejected, and its ## LLP section says "No doc change needed ... the destination's spelling is ours to choose" while this head adds normative R1a pinning exactly that spelling and mandating the lookup command. Whoever squashes this inherits that text as the commit message.

One carried-forward item, yours to weigh: commands/central.js:384 and :524 are hyp leave success surfaces printing bare origins, so they fall outside both the code comment's "error paths" carve-out and R1a's "enrolling login" scope. Closing #391 here is legitimate since its scope really is the enrollment line, but nothing will track those two afterwards.

Advisory as always: neutral does not ready or merge contributor PRs, so landing this is your call.

@bgmcmullen
bgmcmullen merged commit 701982c into master Jul 28, 2026
8 checks passed
@bgmcmullen
bgmcmullen deleted the name-server-not-url-391 branch July 28, 2026 00:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

neutral:approved neutral reviewed this and holds it for a maintainer merge (own or adopted PR; LLP 0025/0030) neutral:review Delegate this PR to neutral for a review pass (approve or request changes; never merges)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Printed forwarding host opens a generic 404 page

2 participants