Repository navigation
feat(link): join a Home from a standalone Child dashboard - #5818
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: Repository: lidge-jun/opencodex/.coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (139)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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 |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
✅ Deterministic PR hygiene checks passed. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 30ff19b29c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (isAlive(pidfile.ownerPid)) return { tunnel: "owned" }; | ||
| const platform = deps.platform ?? process.platform; | ||
| if (platform !== "linux") return { tunnel: "unresolved", pid: pidfile.pid }; |
There was a problem hiding this comment.
Verify unresolved tunnels before marking them connected
On macOS, after the runtime owner exits, this returns unresolved without checking whether the recorded tunnel PID is even alive. createClientLinkSupervisor.initialize() then marks every unresolved tunnel as connected and returns without owning or monitoring a child, so after a crash where SSH also exited—or whenever the orphan later exits—the client permanently relays requests to a dead port and never respawns the tunnel. Check liveness before returning unresolved, and represent a live-but-unverifiable orphan as failed rather than indefinitely connected.
AGENTS.md reference: src/AGENTS.md:L15-L17
Useful? React with 👍 / 👎.
| } catch (error) { | ||
| return joinFailure(error); | ||
| } finally { | ||
| joinInProgress = false; |
There was a problem hiding this comment.
Keep the join latch set after a successful commit
After a successful join, scheduleRestart only schedules shutdown after a response-flush delay, but this finally immediately clears the process-wide latch. A second authenticated POST /api/link/join during that window is admitted despite the already-committed client connection; it can overwrite the first sidecar, fail in connectClient, and then roll back by deleting that replacement sidecar, leaving the pending restart with a link connection but no tunnel state. Clear the latch only on join failure, and retain it until the scheduled restart terminates the process.
AGENTS.md reference: src/AGENTS.md:L15-L17
Useful? React with 👍 / 👎.
| const supervisor = linkMode && existsSync(clientLinkStatePath()) | ||
| ? createClientLinkSupervisor({ | ||
| onLinkEnded: () => scheduleStandaloneRecycle(state.value.tokenFingerprint), | ||
| }) | ||
| : null; |
There was a problem hiding this comment.
Start the link supervisor even when the sidecar is absent
When the persisted connection says transport: "link" but client-link.json is missing at startup, this condition disables the supervisor entirely. The machine listener still reports ready and relays requests to the saved tunnel port, but no SSH tunnel is created and no onLinkEnded recycle can run, so the client remains indefinitely connected-but-unusable after sidecar loss. Create the supervisor for every link-mode runtime and let it treat a missing sidecar as an ended or failed link.
AGENTS.md reference: src/AGENTS.md:L15-L17
Useful? React with 👍 / 👎.
리뷰 · 우선순위 64 / 80이 PR은 원격 링크의 여섯 번째 층이다. 혼자 쓰는 컴퓨터가 자기 대시보드에서 집을 찾아 자식으로 붙는다. 지금까지 자식 역할은 자리만 있었고, 연결은 집에서만 시작했다. 혼자 쓰는 설치에서 자식을 고르면 SSH 호스트를 고르고, 접속을 시험하고, 지문을 확인한 뒤 연결한다. 이 컴퓨터가 열쇠는 명령 인자에 넣지 않는다. 응답과 로그에도 열쇠 본문은 안 남긴다. 집의 라인 - 라인 - 라인 - 메인테이너의 판단이 필요한 지점 재시작이 끝날 때까지 조인 잠금을 유지해 달라. 성공한 조인에서 맥과 윈도우에서 확인 못 한 터널은 실패한 연결로 남겨 달라. 그 프로세스를 죽이지 않는 정책은 그대로 둬도 된다. 집에서 연 링크는 사이드카 없이 그대로 두고, 자식이 연 연결인데 파일만 없으면 실패로 보라. 너의 추천 방향은 맞다. 스택에 두고, #5807 이 이 댓글은 grok-bot이 작성했습니다 |
2b47b70 to
6fa1256
Compare
…r, and the tunnel API
The client runtime starts a supervised ssh -L tunnel when a link sidecar exists, stops it before the listener on every shutdown path, and recycles to standalone once the link ends. A pidfile with the owner pid lets a dead owner's tunnel be reaped on Linux by exact argv; other platforms report it as unresolved. An invalid sidecar fails closed and surfaces as a failed child with reason sidecar_invalid.
…nt port contract ocx disconnect on a Child makes one SSH attempt to run ocx link revoke on the Home, reports homeRevoke and tunnel in --json, and prints the manual revoke command when the attempt fails. disconnectClient removes the sidecar under the lifecycle lock only when it names the disconnected link. The client tunnel port is 1024-65535 everywhere it is accepted.
POST /api/link/join {alias} runs the client-initiated sequence: confirmed host, local port, remote ocx link issue over SSH, sidecar, tunnel readiness with the issued key, in-process connect, then a restart into the client runtime. Failures after the issue roll back the tunnel, the Home link, and the sidecar. Only a paired dashboard session on a standalone runtime may call it.
The Child role is available only on a standalone runtime and reuses the add sheet: candidates, probe, fingerprint confirmation, then join. Joining, failure with Retry, and the restart wait have their own states and strings in all ten locales, and the fixture server can show each of them.
The eight Remote Link guides describe the Child-initiated flow, its SSH requirement, restart, and disconnect behavior. structure/remote-link.md records the join gate, sidecar, tunnel ownership, orphan rule, teardown, and port contract.
…empotent A join rollback clears the sidecar only after the Home revoke succeeds; otherwise it keeps the sidecar and returns join_rollback_failed with the link id, and the next join retries that revoke first. A restart that cannot be scheduled after the committed connect returns join_restart_failed and keeps the connection. ocx link revoke exits 0 for a link the Home no longer has, and disconnect reaps an orphaned tunnel even when the sidecar is corrupt.
Each candidates, probe, confirm, apply, or join attempt has an identity and an abort signal, so a response that arrives after the sheet closed or a newer attempt started changes nothing. The role hint describes the Child flow as it now works, the obsolete childPending notice is gone, the screenshot fixture no longer reaches into the page component, and the two new join errors have their own messages.
The Find Home panel reused the Home empty text, so a standalone Child read that it had no child computers.
The add-child and Find Home sheet was pinned to the right edge with only its left corners rounded, so on a wide screen it floated at the side, detached from the page. It now opens centered like the disconnect confirmation; narrow screens keep the bottom sheet.
… sheet The SSH host alias field rendered as a bare browser input with a bold label, unlike every other form in the dashboard. It now uses the shared .input and .field-label styles, matching the candidate cards and the fingerprint box beside it.
9caa83d to
9aa953e
Compare
Summary
A standalone computer can now join a Home from its own dashboard. Before this layer the Child role on
#remotewas a placeholder, and a link could only start from the Home (ssh -R, #5801–#5807). This is layer L6 of the Remote Link stack and targets #5807.On a standalone runtime the Child role opens Find Home: pick an SSH host, test the connection, confirm the host-key fingerprint, then Connect as Child. The computer restarts into the client runtime, which owns an
ssh -N -Ltunnel to the Home's link listener.ocx disconnecton the Child revokes the link on the Home over SSH.POST /api/link/join {"alias"}accepts only a paired dashboard session on a standalone runtime. A Tailscale identity session gets403, another role gets409 standalone_required, and both checks run before link state is read. The sequence is: confirmed host, a local port of 1024 or higher,ocx link issueon the Home over SSH, theclient-link.jsonsidecar (0600), the tunnel, an authenticated/readyzwithin 15 s, in-processconnectClient, then a restart. A failure after the issue stops the tunnel, revokes the Home link, and clears the sidecar only if the revoke succeeded. Otherwise it returnsjoin_rollback_failedwith the link id, and the next join retries that revoke first. A restart that cannot be scheduled after the committed connect returnsjoin_restart_failedand keeps the connection.src/client/link-tunnel.tssupervises the tunnel with the existing reducer and backoff. It stops the tunnel (TERM, 5 s, then KILL) before the listener on every shutdown path, and recycles to standalone once the link ends. A pidfile records the owner pid, so a tunnel is reaped only when its owner is dead: on Linux by exact/procargv, while elsewhere it is reported asunresolvedand never killed. An invalid sidecar fails closed and shows as a failed child withsidecar_invalid. A link client without a sidecar is the Home-initiated case and is unchanged.ocx disconnectaddshomeRevokeandtunnelto--json. When the Home revoke fails it printsHome revoke failed; run ocx link revoke --link-id <linkId> on the home.ocx link revokeis now idempotent:404 link_not_foundexits 0 because removal revokes the key before it deletes the record. The CLI keeps only a validated error code from an API error body, never the message.src/link/ports.ts(isLinkPort, 1024–65535) covers the client tunnel port wherever it is accepted. The config schema restates the range, because it sits on every install's core path and may not import link code.remote-linkguides replace "coming soon" with the Child flow, andstructure/remote-link.mdrecords the contracts above.Security review is requested: this layer runs SSH commands, carries the issued data key in memory from the
issueoutput intoconnectClient, and changesocx link revokeerror handling. The key is never logged, returned, written outside the existing client connection config, or placed in argv, andlink-join-route.test.tsasserts that for every response and console line.Screenshots
Standalone role choice, then Find Home with the host confirmed:
A join failure with Retry, then the restart wait after a successful join:
A Home runtime shows the Child role disabled with its reason; joining on mobile:
All 21 captures (en/ko desktop, en mobile) are under
pr-assets@df24377da4/260925-remote-link-child, taken withgui/scripts/remote-link-fixture.tsin headless Chrome by clicking through the real flow.Verification
30ff19b: 34 root files covering every link, disconnect, layout, file-size, headless-parity, structure, core-lab and repo-hygiene suite, with 360 pass and 0 fail.cd gui && bun test tests: 2,367 pass, 0 fail.bun x tsc --noEmit,bun run structure:check,bun run privacy:scan,bun run lint,bun run lint:i18n, andbun run buildpass.react-doctor@0.9.11 --scope changedreports no issues.bun run test:changedfails locally in this worktree for environment reasons. L6 and its parent L5 fail the identical 946 test names, so L6 adds no new failure. The full suite is left to CI.tests/server/link-join-route.test.tscovers the gates, the a→h order, every rollback point, stale-sidecar compensation, and the restart failure.tests/clients/client-link-tunnel.test.tscovers argv, TERM/KILL, the stop trigger within two ticks,sidecar_invalid, and the reap rules. The other new suites areclient-link-teardown,client-link-state, andlink-ports, plus revoke idempotency incli-link. GUI tests cover the standalone gate, the exact join body, Retry, stale responses after cancel, and the new error codes. All five new root tests are registered in both layout maps./readyzstep runs against fakes. On macOS an orphaned client tunnel is reported, not reaped.Checklist
structure/remote-link.md,structure/runtime.mdone sentence at its 600-line budget)