Skip to content

fix(client): tell the dashboard it is a client - #3169

Merged
lidge-jun merged 1 commit into
devfrom
codex/machine-listener-role-tag
Sep 1, 2026
Merged

fix(client): tell the dashboard it is a client#3169
lidge-jun merged 1 commit into
devfrom
codex/machine-listener-role-tag

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • The machine listener served the dashboard without the opencodex-runtime-role meta tag, so a connected client rendered as a plain standalone install.
  • The tag is load-bearing, not decoration. gui/src/api-targets.ts reads it in isConnectedRuntime(), and discoverApiTargets() returns standalone targets immediately when it is anything other than client — deliberately, so a user who never enabled remote hub issues no request to a remote-hub endpoint. On a real client that shortcut fired every time: discovery never queried /api/machine/status, so there was no hub usage scope on Usage, no "this machine" panel on Startup, no connected-client list on Integrations, and no pairing form.
  • src/server/index.ts:1963 already passes config.runtimeRole on the same call. This listener only ever serves a connected client, so the role is a constant here rather than a config read.

Found while documenting what a connected client actually shows, which is how the gap surfaced: the documentation claimed a two-plane dashboard the running code could not produce.

Verification

Verified by hand on a live listener with a real gui/dist, before and after:

  • Before: the served document carried only the session meta. The dashboard showed the standalone layout.
  • After: <meta name="opencodex-runtime-role" content="client"> is present and the two-plane UI appears — "Disconnect from hub" in the sidebar and the "Connect this dashboard to the hub" pairing form.

Tests:

  • bun test tests/client-machine-listener.test.ts — 6 pass, 0 fail.
  • bun test tests/client-machine-listener.test.ts tests/core-lab-boundary.test.ts — 23 pass, 0 fail.
  • bun x tsc --noEmit — clean.

Both new tests were driven red against the unfixed call before the fix landed. The listener-level one reads the source rather than the HTTP response on purpose: the listener falls through to a JSON payload when gui/dist is absent, so an HTTP-level assertion would pass vacuously in a checkout with no GUI build. The document-level one covers serveGuiFile directly with a temporary dist.

No full suite, per the no-local-suite policy.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed — behavior returns to what the docs already describe; no user-facing contract changes.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults. The tag is non-secret by design (it names a topology the operator configured), and it is what keeps a standalone install from probing remote-hub endpoints. Admission is unchanged: the machine API still requires a GUI session.

Summary by CodeRabbit

  • Bug Fixes
    • Improved GUI behavior when connected through a machine listener by correctly identifying the client runtime role.
    • Ensured served GUI pages include the appropriate client metadata.

The machine listener served the GUI without the runtime-role meta tag, so a
connected client rendered as a plain standalone install.

The tag is not decoration. gui/src/api-targets.ts reads it in
isConnectedRuntime(), and discoverApiTargets() returns standalone targets
immediately when it is anything other than "client" -- deliberately, so a user
who never enabled remote hub issues no request to a remote-hub endpoint. The
consequence on a real client was that discovery never queried
/api/machine/status: no hub usage scope on Usage, no "this machine" panel on
Startup, no connected-client list on Integrations, and no pairing form.

src/server/index.ts already passes config.runtimeRole on the same call. The
listener only ever serves a connected client, so the role is a constant here
rather than a config read.

Verified by hand before and after: with the tag absent the served document has
only the session meta and the dashboard shows the standalone layout; with it
present the two-plane UI appears, including "Disconnect from hub" and the
pairing form.

The regression test asserts the call carries the role. It reads the source
rather than the HTTP response because the listener falls through to a JSON
payload when gui/dist is absent, which would make an HTTP-level assertion pass
vacuously in a checkout with no GUI build. A second test covers the document
itself through serveGuiFile with a temporary dist. Both were driven red against
the unfixed call.
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 1, 2026 15:57
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-01T15:59:07.625967Z adac530 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Sep 1, 2026
@lidge-jun
lidge-jun merged commit e40245e into dev Sep 1, 2026
23 of 24 checks passed
@lidge-jun
lidge-jun deleted the codex/machine-listener-role-tag branch September 1, 2026 16:11
@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Team

Run ID: 783f424b-fea3-4efd-87c3-a17ec5ed73d0

📥 Commits

Reviewing files that changed from the base of the PR and between 2f2629f and adac530.

📒 Files selected for processing (2)
  • src/client/machine-listener.ts
  • tests/client-machine-listener.test.ts

📝 Walkthrough

Walkthrough

The client machine listener now passes the fixed client role to serveGuiFile. Tests verify the generated GUI metadata and the source-level wiring.

Changes

Client GUI role tagging

Layer / File(s) Summary
Role wiring and validation
src/client/machine-listener.ts, tests/client-machine-listener.test.ts
At lines 121–128, machine-listener.ts passes "client" to serveGuiFile. Tests add imports and verify the opencodex-runtime-role="client" metadata and the source-level fallback at lines 159–196.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: ingwannu

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/machine-listener-role-tag

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 60 / 80

설명

이 PR은 연결된 클라이언트(machine listener)가 대시보드 HTML을 줄 때 opencodex-runtime-role 메타 태그를 빠뜨리던 구멍을 막습니다. 지금 dev HEAD e40245e4c 에 이미 머지되어 있고, 제목 그대로 fix(client): tell the dashboard it is a client (#3169) 입니다.

왜 중요한가. GUI는 gui/src/api-targets.tsisConnectedRuntime() / discoverApiTargets() 가 문서 안의 역할 태그만 보고 동작합니다. 태그가 client 가 아니면 곧바로 standalone 타깃을 돌려주고 /api/machine/status 를 아예 안 칩니다. 의도된 설계입니다 — 원격 허브를 켠 적 없는 사용자에게 허브 엔드포인트를 찔러 보지 않게 하려는 것. 그런데 machine listener는 원래부터 연결된 클라이언트만 서빙하는데, serveGuiFile(url.pathname, undefined, session) 네 번째 인자(역할)를 안 넘기고 있었습니다. 그래서 진짜 클라이언트에서도 태그가 없고, 대시보드가 단독 설치처럼 보였습니다. Usage의 허브 사용량 범위, Startup의 this machine 패널, Integrations의 연결 클라이언트 목록, 페어링 폼이 안 나옵니다.

고친 곳. src/client/machine-listener.ts 한 줄이 핵심입니다. 이제 serveGuiFile(..., "client") 로 상수 역할을 넘깁니다. 같은 저장소의 src/server/index.ts 약 1963행은 이미 config.runtimeRole ?? "standalone" 을 넘기고 있고, src/server/gui-static.tsruntimeRoleMeta / htmlResponse</head> 앞에 메타를 꽂습니다. listener는 연결 클라이언트만 뜨므로 config를 읽을 필요 없이 상수 client 가 맞고, 같은 파일의 JSON 폴백(role: "client")과도 맞습니다.

테스트. tests/client-machine-listener.test.ts 에 두 개를 추가했습니다. 하나는 임시 gui/distserveGuiFile 문서를 직접 열어 메타 content가 client 인지 확인합니다. 다른 하나는 listener 소스를 읽어 serveGuiFile(...) 호출 인자에 "client" 문자열이 들어 있는지 봅니다. HTTP로 안 돌리는 이유는 PR 본문대로 — dist가 없으면 listener가 JSON으로 떨어지고, 그때 HTTP 단언은 빈 통과가 되기 쉽습니다. 이미 있던 meta() 헬퍼를 그대로 씁니다.

현재 dev 방향과의 관계. remote hub restack(#2771 계열)과 skill 문서(#3165)가 막 올라온 뒤, 연결된 GUI가 문서로 약속한 두 평면 UI를 실제로 보여줄 수 있게 만드는 필수 한 조각입니다. 문서만 있고 런타임 태그가 빠지면 문서와 제품이 어긋납니다. 후속 후보로 스냅샷에 적힌 #3168(인증된 GET /api/system/health)은 별개 축입니다. types.ts/config.ts 분할 캠페인과 겹치지 않고, 중복 PR도 아닙니다.

라인 128 - 상수 "client" 는 이 listener 전제(연결 클라이언트만 기동)와 루트 JSON 폴백의 role: "client" 와 일치한다. 잘못 끼워 넣은 하드코딩이 아니다.
라인 184-194 - 소스 정규식 /serveGuiFile\(([^)]*)\)/ 는 첫 매치만 본다. 지금은 호출이 하나라서 맞지만, 같은 파일에 두 번째 serveGuiFile 이 생기면 엉뚱한 호출을 잡을 수 있다. 의도적으로 취약한 단언이다.
테스트 serveGuiFile 직접 호출 - 문서 주입기(gui-static)가 태그를 넣는지만 증명하고, listener 배선은 소스 단언에 맡긴다. dist 없는 CI에서 HTTP 단언이 빈 통과하는 문제를 피하려는 선택이라 이해되지만, listener→serveGuiFile 통합 HTTP 경로는 여전히 커버되지 않는다.
경로 serveSessionBootstrap - /opencodex-session 문서는 역할 메타를 안 넣는다. Vite/세션 부트스트랩 전용이라 정상 대시보드 / 경로와는 다르다. 다만 Vite에서 연결된 클라이언트를 흉내 내려면 역할 태그를 따로 넣어야 한다 (없으면 api-targets 가 standalone으로 읽음 — 주석상 안전 기본값).

메인테이너의 판단이 필요한 지점

  • 이미 dev 에 머지됨(state=MERGED). 리뷰는 기록용이다.
  • 소스 단언을 유지할지, dist가 있을 때만 HTTP 통합 단언을 조건부 추가할지.
  • Vite/세션 부트스트랩에 역할 태그를 넣을지, 아니면 문서/스킬에 “dev GUI는 standalone으로 읽힌다”고만 남길지.
  • 다음 remote-hub GUI 후속(fix(remote): restore authenticated GUI health #3168 authenticated health 등)과의 순서만 잡으면 된다.

너의 추천
이미 머지·검증된 수정이다. 추가 변경 없이 닫힌 상태로 두면 된다. 후속은 #3168(연결된 Dashboard/memory restart 폴링이 management-denied /healthz 를 치지 않게)을 remote-hub GUI 축의 다음 조각으로 보면 된다. types/config 분할에 무효화될 PR이 아니므로 close-don't-rebase 대상도 아니다.

이 댓글은 grok-bot이 작성했습니다

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

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant