docs(remote-hub): the one-port hub recipe, invite flow, and launchd semantics (en+ko, skill) - #4254
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (2)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
✅ Deterministic PR hygiene checks passed. |
리뷰 · 우선순위 72 / 80이 PR은 #4236 허브 단일 포트 스택의 5번째(마지막) 조각입니다. 지금 지금 설정 블록도 한 포트 레시피로 바뀝니다. 새로 생긴 두 절도 코드와 맞습니다. 허브 자신의 로컬 클라이언트는 companion이 받는 와이어( 영어·한국어 가이드와 한 가지 합류 전에 꼭 볼 점: 이름 붙인 베이스 라인 단위로 보면 아래입니다. docs-site/.../guides/remote-hub.md (en) 설정 블록 - 한 포트 companion + 메인테이너의 판단이 필요한 지점
너의 추천
이 댓글은 grok-bot이 작성했습니다 |
e044679 to
55d0773
Compare
55d0773 to
1e792cb
Compare
2aa572d to
2e90005
Compare
1e792cb to
30d5984
Compare
…tics (en+ko) The guide told the operator to export a data-plane token by hand before `ocx service install`, described the loopback listener only in its ported form, and had no path for joining a second machine other than assembling `ocx connect` from parts. All three stopped being true in this stack (#4236). English and Korean now share one structure. The setup block is the one-port recipe: hostname is the tailnet IP, `unauthenticatedLoopbackListener: {"enabled": true}` binds the companion socket on the same port, and `hub.dataPublicOrigin` sits beside `hub.managementPublicOrigin` so the printed invite is usable. The `export OPENCODEX_API_AUTH_TOKEN="$(openssl rand -hex 32)"` line is gone, replaced by the real precedence (env, then the existing owner-only file, then 32 fresh bytes) and the fact that an ADMIN token there is refused. New sections cover the ported form as the documented alternative, the hub's own local clients with the exact admitted wire list and `count_tokens` named as absent, `ocx hub invite` with the `corsAllowOrigins` precondition and the reason `--management-url` can only confirm, and macOS service operations. Two corrections the code supports and the plan's wording did not. `ocx service restart` aliases `repair`, and after PR1 a repair of a healthy launchd job is a no-op, so it restarts nothing -- `launchctl kickstart -k gui/$(id -u)/com.opencodex.proxy` is the way to bounce it. And Tailscale Serve must not target the companion listener: it is a real socket, so the mapping is accepted, and then the loopback admission policy's `Host` check rejects the forwarded Host exactly as the plain loopback-bind trap does. The forwarder section stays, with its example port moved off 10100 because the companion owns that address now. #4241's structure and every claim its guard test pins are intact. The guard was extended, not relaxed: five new cases over both locales pin the companion form, the ported alternative, `ocx hub invite` with its precondition, the kickstart line, the four-state launchd wording, and the Serve warning. The no-exported-token assertion is line-anchored so prose can still name the variable -- the page has to, to say the step is gone and that an admin token is refused there. `reference/configuration/server.md`: `hub.dataPublicOrigin`, how the two public origins relate, and the companion refusal rule. The `hostname` row and `## Remote access` no longer claim a non-loopback bind refuses to start without the environment variable, which stopped being true in PR4. The Korean page had neither the loopback-listener section nor the Remote Hub key table; both are written. The other five translations are out of scope and still carry the old recipe. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…hd no-ops The skill described a hub whose only interesting decisions were pairing and disconnection. After #4236 three more things are wrong to guess, and each of them is a thing an agent would otherwise report as a bug. `SKILL.md` leads with the topology: one port, the companion listener, `ocx hub invite` instead of a hand-built `ocx connect`, `--management-url` as a confirmation, and "read the `Hub:` block in `ocx status` before asking the operator about ports or tokens". `05_remote_hub.md` gains the listener's two forms and the collision refusal, the exact list of admitted wires with an explicit "do not propose widening it to `/api/*`", the hub gate on the hub's own clients quoted verbatim, the invite flow including the `No loopback browser origin is admitted for pairing` refusal and the command that fixes it, and a section on the data token that says what NOT to do with it: never tell an operator to export one, never regenerate it to fix something, never copy the file to another machine. `04_failure_semantics.md` gains the two states that read as failures and are not. A repair printing `nothing to do` is success. `ocx service restart` aliases `repair`, so on a healthy macOS job it restarts nothing -- which matters precisely when a restart is the requirement. The four launchd verdicts get a per-row "repair?" column, because `launchd state could not be verified` is an unanswerable probe, not a down service, and treating it as one is what used to send operators to repair a serving hub. A hub-gated skip exits 0 having written nothing and has nothing to retry. `03_recipes.md` gains recipe 10: read the hub block, `ocx hub invite --json`, hand over `command`, and the two refusals that burn no code. `01_management_surface.md` is generated and was left alone; `skill:surface:check` reports it current. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…url only confirms Read as a first-time hub operator, PR4's help text is accurate; three things were not. `ocx service` had exactly one line about `restart`, and it was Windows-specific. An operator reading it infers that `restart` restarts the service, which on macOS is false after PR1: `restart` aliases `repair`, and a repair whose plist, token file and loaded job already agree returns early without touching launchd. The details now say so and name `launchctl kickstart -k gui/$(id -u)/com.opencodex.proxy` (or stop then start) for the case where a bounce is the actual requirement -- a change to hostname, port or `unauthenticatedLoopbackListener`. The same block now lists the four `ocx service status` verdicts, so `launchd state could not be verified` is read as an unanswerable probe rather than a down service. `ocx hub`'s usage line advertises `--management-url`, and nothing said it is a confirmation of `hub.managementPublicOrigin` rather than an override. An operator reading only the help would pass a different value and hit a refusal. `--clients` was undocumented too. The top-level banner's `ocx status` line now mentions the hub block, because nothing in `ocx --help` pointed at the one command that answers "what is this hub doing". No behavior change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Records the en+ko rewrite, the reference-config additions, the skill pages, the help corrections, the extended guard test, and the two findings that changed the copy: a macOS `ocx service restart` does not bounce a healthy job, and the loopback companion listener must not be a Tailscale Serve target. Names the five translations left out of scope and exactly what they still claim. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…es and the skill The base branch grew a four-commit review round while this unit was being written, and four of its findings made sentences here false. Reading src/ at the new base rather than trusting the first draft is what caught them. `ocx hub invite` now REFUSES a loopback- or wildcard-derived data origin instead of advertising `http://localhost:<port>`, which would tell the other machine to dial itself and spend the single-use code. So the resolution order is `--data-url`, then hub.dataPublicOrigin, then the bind address -- and that last step only works when the bind is an address another machine can dial. "...or http://<bind>:<port> as a last resort" would have walked an operator on a loopback-bound hub into the refusal with no idea why. An explicit override is never second-guessed, because a loopback data origin is legitimate over an SSH tunnel. The reused `service-api-token` file is re-checked for the admin token now, with a different remedy: delete the file and run `ocx service repair`, because unsetting a variable says nothing about a file. Both collision checks run ahead of the loopback short-circuit, since the wrapper reads that file into the variable whatever the hostname. Every page that said the existing file is reused now says it is re-checked, not trusted. The `ocx status` token states changed. `present (env)` is gone -- the state is always about the file (`present (file)`, `unsafe (file)`, `admin-collision (file)`, `missing`) and the shell's variable is a separate sub-line. Listing `present (env)` as a state was exactly the honesty defect the review round fixed in the code. Every successful invite prints a `Bound browser origin:` line on stderr, and it is NOT in the --json envelope. A grant is bound to one origin while a remote `ocx connect` presents `Origin: http://localhost:<its own port>`, so a non-default bound origin means the other machine must already be on that port or the exchange is refused and the code is spent. The skill's recipe now says to relay that line, which an agent reading only --json would drop. One smaller correction rode along: the `corsAllowOrigins` fix command preserves existing entries, so all three places that quote it say to run the line `invite` prints rather than a hand-written one-element array. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The base (#4249) gained `ee6a20a0e` after this unit was written, which inverts the claim five of these files had just landed. `restart` no longer folds into `repair`: it runs the same refresh and, when nothing was reloaded, runs `launchctl kickstart -k gui/<uid>/com.opencodex.proxy` in place, verifies with the launchd probe, and prints `service restarted (launchctl kickstart -k …)`. `repair` keeps the no-op -- a repair of a healthy service must not be an outage -- and a bare `ocx service` still selects `repair`. Linux always restarted (`systemctl --user restart`); Windows is unchanged. So every passage saying restart aliases repair / restarts nothing / telling the operator to kickstart by hand now names `ocx service restart`, and `launchctl kickstart -k` is demoted to the manual fallback the failure path itself prints. Both guides, the two skill references and `SKILL.md` also distinguish `ocx restart` (the proxy process you started) from `ocx service restart` (the service the manager supervises) wherever a restart is prescribed; `src/cli/help.ts`'s `ocx restart` is a different verb and is untouched. `src/cli/registry.ts`'s `service` entry was already reconciled in the base -- the docs were made to match it, not the reverse. The docs-claims gate pins the new claim and forbids the old "is an alias of `repair`" sentence, while keeping the kickstart line pinned only alongside the words "manual fallback", so the page cannot quietly promote it back to the recommended route. No `src/` change. Verified: the four focused test files (57 pass, 0 fail), `bun run typecheck` clean, `bun run privacy:scan` passed, and the docs-site build (425 pages, Complete!). No `ocx service …` and no `launchctl` command was run on this host, per the operator's instruction. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…pair A repo-wide grep for the old claim found two pages outside the previous commit's file list, and both quote code that `ee6a20a0e` changed: - `reference/cli/lifecycle.md` (en + ko) had `| restart | Alias of repair. |` in the `ocx service` subcommand table. It now describes the two verbs separately, matching `src/cli/registry.ts`: `repair` reloads the manager only when something changed, so a healthy unchanged macOS job keeps running; `restart` is the same refresh but always restarts, kickstarting an unchanged loaded job in place. - The `ocx status` version-skew paragraph on both pages still printed the pre-`ee6a20a0e` advice, `ocx service repair (ocx service restart is an alias)`, which `src/cli/version-skew.ts` no longer emits: a skew leaves the definition byte-identical, so repair reloads nothing and keeps the old process serving. Both now say `ocx service restart` and why. The bare-`ocx service` row and the `repair` row also stop claiming an unconditional restart — a gap PR1 left rather than one this round created. The other five locales of `lifecycle.md` still carry both old rows and are recorded in the devlog's Left over, with the same translation-parity follow-up as the rest of this unit. Verified: the four focused test files (57 pass, 0 fail, 519 expect()), `bun run privacy:scan` passed, and `cd docs-site && bun run build` → 425 pages, Complete! No `src/` change. No `ocx service …` and no `launchctl` command was run on this host. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2e90005 to
902fc02
Compare
1f765a5 to
eff7204
Compare
Summary
The documentation and the
ocxskill still described the hub the stack just replaced, and in one place they described something that had become actively harmful.The guide told the operator to export a data-plane token by hand.
export OPENCODEX_API_AUTH_TOKEN="$(openssl rand -hex 32)", immediately beforeocx service install. That line is how the maintainer's own hub ended up with a management admin token in the data-plane variable and crash-looped — the token at hand went into the variable the command demanded. PR4 removed the demand; this PR removes the instruction. In its place: the real precedence (environment, then the existing owner-onlyservice-api-token, then 32 fresh random bytes), that only the path is printed, that a foregroundocx startreads the same file, and that an admin token there is refused withunset OPENCODEX_API_AUTH_TOKENand rerun.The setup block is now the one-port recipe.
hostnameis the tailnet IP,ocx config set unauthenticatedLoopbackListener '{"enabled":true}'binds the companion socket on the same port, andhub.dataPublicOriginsits besidehub.managementPublicOrigin— without itocx hub invitefalls back tohttp://<tailnet IP>:<port>, which is exactly the address a machine behind the TLS frontend cannot use, so the guide would have handed out a broken command. The ported form{"enabled":true,"port":10104}stays documented as the alternative, with PR3's restart caveat stated where an operator hits it rather than in a devlog.Two sections that did not exist. The hub's own local clients records what PR3 shipped:
ocx claude, Claude Desktop, Cursor,system-envand the vision helper work on a hub now, with the exact admitted wire list,count_tokensnamed as not admitted, and why/api/*stays 404 there. Inviting another machine isocx hub invite→ the copy-pasteecho '<code>' | ocx connect … --pairing-code-stdinblock, plus thecorsAllowOriginsprecondition with its exact command and the reason it exists (ocx connectpresentsOrigin: http://localhost:<its own port>; grants are origin-bound).Two corrections the code supports and the plan's own wording did not.
ocx service restartdid not restart a healthy macOS job:serviceCommandmappedrestart→repair,repairServicecallsinstallLaunchd, and after PR1installLaunchdreturns early —service is already loaded from the current plist; nothing to do.— whenever the rendered plist equals the file, the token file is unchanged, andlaunchctl printagrees. There was no stop/start in that path, so a restart had to be a hand-written launchctl command. #4249 then fixed the code instead (ee6a20a0e), and the last commit here follows it — see the restart bullet below. PR1'srepairno-op is still right; what changed is that there is now a verb that does the other thing.Tailscale Serve must not target the companion listener. It is a real socket on
127.0.0.1:<proxy port>, so the mapping is accepted and then fails: the companion runs with a policy view whose hostname is127.0.0.1, which makesisApiAuthRequiredfalse, and that branch ofisAllowedRequestOriginrequires a loopbackHostheader. Serve forwardsHost: hub-name.tailnet-name.ts.net, so the data routes answer403 origin_rejected— #4241's trap, reachable a second way. The forwarder section stays intact and its example port moved off10100, because the companion owns that address now. #4241's loopback-bind trap table is unchanged and now says explicitly that it is about the bind, with the companion named as the sanctioned way to get a127.0.0.1socket on a hub.Scope: English and Korean only. The Korean page existed but was a condensed pre-#4241 copy — it set
hub.managementPublicOriginbefore creatinghub(the #4200 defect, in Korean), exported the token by hand, and offered the retired--allow-insecure-http. It is now a section-for-section mirror of English. The other five locales are a follow-up, matching #4241's own precedent; the devlog names exactly what they still claim.reference/configuration/server.md(en + ko) gainshub.dataPublicOrigin, how the two advertised origins relate, why--management-urlcan only confirm while--data-urlreally overrides, and the companion refusal rule. Itshostnamerow and## Remote accessno longer claim a non-loopback bind refuses to start without the environment variable — that stopped being true in PR4, and a reference page that says the proxy will refuse to start is not a cosmetic inaccuracy. The Korean page had neither the loopback-listener section nor the Remote Hub key table; both are written.skills/ocxgains the topology, the invite flow, the hub gate ("this machine is a hub; local sync is gated unless the loopback listener is enabled" — the exact sentence, with the note that it means the gate and notclientIntegrations), a section on the data token that says what not to do with it, the launchd repair/status semantics with a per-row "repair?" column, and recipe 10 for inviting a machine.references/01_management_surface.mdwas not hand-edited;skill:surface:checkreports it current.Help copy was read as a first-time hub operator and touched in exactly three places, each wrong or missing: the macOS restart/status semantics under
ocx service,--management-urland--clientsunderocx hub, and the banner'socx statusline, which was the one command that answers "what is this hub doing" and had nothing pointing at it.Restacked mid-work, and it changed the copy. This branch was cut from the then-tip of #4252's branch; while it was being written that branch was rebased onto #4249 and grew a four-commit review round, so the original base left its history entirely. The rebase had one conflict (
src/cli/registry.ts'shubdetails — both sides had edited the--data-urlparagraph), resolved by keeping the base's new refusal text and appending this PR's--management-url/--clientslines. The review round was not only text, and four of its findings made sentences in the first draft false; the last commit reconciles all four across both locales, both reference pages and the skill:ocx hub invitenow refuses a loopback- or wildcard-derived data origin instead of advertisinghttp://localhost:<port>. So the order is--data-url→hub.dataPublicOrigin→ the bind address, and that last step only works when the bind is an address another machine can dial. "…orhttp://<bind>:<port>as a last resort" would have walked an operator on a loopback-bound hub into the refusal with no idea why.service-api-tokenfile is re-checked for the admin token, with a different remedy — delete the file and runocx service repair, because unsetting a variable says nothing about a file — and both checks now run ahead of the loopback short-circuit.ocx statustoken states changed:present (env)is gone, the state is always about the file (present (file)/unsafe (file)/admin-collision (file)/missing), and the shell's variable is a separate sub-line. Listingpresent (env)was exactly the honesty defect the review round fixed in the code.Bound browser origin:line on stderr that is not in the--jsonenvelope, and it decides whether the exchange works. The skill's recipe now says to relay it, which an agent reading only--jsonwould drop.ocx service restartrestarts a healthy launchd job now, so the docs stop telling operators to kickstart by hand. A fifth reconciliation, againstee6a20a0eon fix(service): macOS repair no longer evicts a healthy launchd job, and verifies the one it loads #4249, which landed after this unit was written.restartis no longer an alias ofrepair: it runs the same refresh and, when nothing was reloaded, runslaunchctl kickstart -k gui/<uid>/com.opencodex.proxyin place, verifies with the launchd probe, and printsservice restarted (launchctl kickstart -k …).repairkeeps the no-op — a repair of a healthy service must not be an outage — and a bareocx servicestill selectsrepair; Linux always restarted (systemctl --user restart) and Windows is unchanged. Every passage in both guides, both skill references andSKILL.mdthat said restart aliases repair / restarts nothing / "kick the job yourself" now namesocx service restart, with the kickstart line demoted to the manual fallback the failure path itself prints. The guides also distinguishocx restart(the proxy process you started) fromocx service restart(the service the manager supervises) wherever a restart is prescribed.src/cli/registry.ts'sserviceentry was already reconciled in the base — the docs were made to match it, not the reverse — and the docs-claims gate pins the new claim, forbids the old "is an alias ofrepair" sentence, and keeps the kickstart line pinned only alongside the words "manual fallback". A repo-wide grep then found two more pages outside this unit's original file list, both quoting codeee6a20a0echanged, and both fixed:reference/cli/lifecycle.md(en + ko) had| restart | Alias of repair. |in theocx servicetable, and itsocx statusparagraph still printed the advicesrc/cli/version-skew.tsstopped emitting (ocx service repair (ocx service restart is an alias)) — a skew leaves the definition byte-identical, so repair would no-op over the old process. The other five locales of that page are recorded in the devlog's Left over with the rest of the translation-parity follow-up.No runtime behavior changes. The only
src/edits are help and registry strings.Verification
tests/ci-workflows/docs-remote-hub-claims.test.tsextended from 7 to 12 tests, five new cases run over both locales: the companion form and the ported alternative are both present;ocx hub invitewith itscorsAllowOriginsprecondition and--pairing-code-stdin; the kickstart line and thelaunchd state could not be verifiedstate; and the Serve warning. The no-exported-token assertion is line-anchored (/^\s*export\s+OPENCODEX_API_AUTH_TOKEN/m) rather than a substring ban: prose has to stay free to name the variable, because the page must say the step is gone and that an admin token is refused there. The first draft used a substring and failed on its own page, which is how the anchor got chosen. docs(remote-hub): make the setup runnable on a fresh config and close the macOS data plane #4241's seven assertions are untouched — extended, never relaxed.bun test tests/ci-workflows/docs-remote-hub-claims.test.ts tests/ci-workflows/skill-ocx.test.ts tests/cli/cli-help.test.ts tests/cli/cli-registry.test.ts→ 57 pass, 0 fail, 515 expect()bun test tests/cli/cli-capabilities.test.ts→ 17 passbun test tests/ci-workflows/docs-429-failover-claims.test.ts tests/ci-workflows/docs-provider-billing-claims.test.ts tests/ci-workflows/docs-readme-translation-parity.test.ts tests/ci-workflows/docs-bun-source-requirement.test.ts→ 64 pass (the other docs-claims gates)bun run typecheck→ clean ·bun run privacy:scan→ Privacy scan passed ·bun run skill:surface:check→ currentdocs-site/AGENTS.mdrequires a docs build for anydocs-site/change, and it passed:cd docs-site && bun install --frozen-lockfile && bun run build→ 425 pages built, Complete! On top of that, every in-page anchor was checked against the generated HTML rather than by eye: all six new English ids exist indist/guides/remote-hub/index.html, and a script compared everyhref="#…"against everyid="…"indist/ko/guides/remote-hub/index.htmland bothserver/index.htmlpages — zero unresolved, including the percent-encoded Korean anchors.bun test tests/ci-workflows/docs-remote-hub-claims.test.ts tests/ci-workflows/skill-ocx.test.ts tests/cli/cli-help.test.ts tests/cli/cli-registry.test.ts→ 57 pass, 0 fail, 519 expect() (four new assertions on the launchd-semantics gate),bun run typecheck→ clean,bun run privacy:scan→ Privacy scan passed, andcd docs-site && bun install --frozen-lockfile && bun run build→ 425 pages built, Complete! again. Nosrc/change in that commit.bun run src/cli/index.ts help huband… help service, to read the rendered copy. Noocx service …, noocx start/stop/ensure/sync/restore, and nolaunchctlcommand was run. The two corrections above were established by readingsrc/service.ts(serviceCommand→repair,repairService→installLaunchd, the early return) andsrc/server/auth-cors.ts(isAllowedRequestOrigin's loopbackHostrequirement), not by probing the hub.Checklist
Part of #4236.