Skip to content

fix(desktop): route Skill Hub through the proxy-aware stack and refuse off-host project installs - #363

Merged
quickbeard merged 1 commit into
mainfrom
skillhub-proxy-scope
Sep 11, 2026
Merged

quickbeard merged 1 commit into
mainfrom
skillhub-proxy-scope

Conversation

@quickbeard

Copy link
Copy Markdown
Owner

Problem

Two gaps in the desktop Skill Hub integration, both in the enterprise setups this fork targets:

  1. Hub traffic bypassed the corporate proxy. The gateway SSO calls run through Electron's net.fetch so system proxies, PAC files, and enterprise roots apply — but @codev/skillhub used Node's global fetch, which honors none of them, and the silent SSO refresh inside it ran on Node's fetch too. Behind a proxy, sign-in worked while the Skill Hub page showed "Couldn't reach the Skill Hub".
  2. Project-scope installs assumed the project lived on the host filesystem. The install dialog offered every project of the connected server, but the main process writes to the host disk. Against a WSL distro, a sandbox, or a remote server the worktree is not on the host, so the install landed in the wrong place (a Linux path resolving to C:\home\… on Windows) or failed with an unhelpful error. Nothing filtered or warned.

Fix

Proxy-aware hub transport

  • @codev/skillhub: the existing httpApi seam gains an injectable transport and an exported setHubTransport(fetchImpl). silentSso() inside the package now receives the same transport, so the token refresh that authenticates hub requests follows the proxy too. SKILLHUB_URL is exported for the desktop wiring.
  • Desktop createSkillHubController takes optional fetchImpl / warmProxy deps. Main passes the same net.fetch wrapper the gateway SSO controller uses, and session.defaultSession.resolveProxy(SKILLHUB_URL) as the warm-up — awaited once before the first hub request, bounded to 60 s, never blocking on failure — so Chromium's proxy initialization is not charged against the hub's 15 s request timeout (the same failure mode fix(desktop): stop the SSO setup timing out on Chromium's network stack #360 fixes for SSO). The resolved proxy is logged for the next report from such a network.
  • Chromium's net::ERR_* messages classify as network errors so the page keeps its hub-unreachable state.

Off-host project installs refused, not misplaced

  • The install dialog reads server.isLocal(). Off the local server, This project is disabled, the scope defaults to global, and a note names the connected server and points at codev skill pull on that machine (a global install still lands on this computer, which that server will not see — the note says so).
  • Backstop in the main process: a project-scope install checks the folder exists on the host before any hub traffic and returns a new missing-directory error code, rendered with a localized message. New keys in en, es, vi.

Tests

  • packages/codev-skillhub: 196/196 pass, including two new transport tests — hub requests use the installed transport; the silent SSO refresh behind a request (token + userinfo) uses it too and the hub request carries the refreshed bearer.
  • packages/desktop src/main/skillhub: 9/9 pass, four new — a missing project folder is refused before the hub is contacted; net::ERR_* classifies as network; requests wait for the warm-up once and it runs exactly once; a failed warm-up does not block.
  • bun typecheck clean in packages/codev-skillhub, packages/desktop, packages/app, and the root bun typecheck (the pre-push hook) on this branch.

Not verified on a real proxied network or against a WSL server (neither available here).

DIVERGENCES.md (desktop Skill Hub wiring row) and CHANGELOG.md updated. Note: packages/desktop/src/main/index.ts is also touched by the in-flight gateway-fetch-fallback branch; the hunks are adjacent but distinct.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QaEgv2JVUbep1sBYApZDFW

…e off-host project installs

Hub browsing and installs in the desktop app went straight to Node's fetch,
which ignores the system proxy and PAC settings that the gateway SSO calls
already honor via Electron's net.fetch. @codev/skillhub now has an injectable
transport (setHubTransport) that also carries the silent SSO refresh, the
desktop passes net.fetch, and the controller waits out Chromium's proxy
initialization once before the first hub request so it is not charged to the
15 s request timeout. Chromium's net::ERR_* codes classify as network errors.

The install dialog offered every project of the connected server, but the
main process writes to the host disk, so a WSL, sandbox, or remote project
landed in the wrong place. "This project" is now disabled off the local
server with a note naming the server and pointing at `codev skill pull`, and
the controller checks the folder exists on the host before any hub traffic
(new `missing-directory` error code).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QaEgv2JVUbep1sBYApZDFW
@quickbeard
quickbeard merged commit 6b4ae10 into main Sep 11, 2026
0 of 5 checks passed
@quickbeard
quickbeard deleted the skillhub-proxy-scope branch September 11, 2026 10:51
quickbeard added a commit that referenced this pull request Sep 14, 2026
…e Electron bridge (#368)

* feat(app): serve the whole Skill Hub through the server and retire the Electron bridge

Single-skill browse, detail, and install join the Skill Stacks on the
server's fork-only v2.skillhub group (skillhub.skill.list / get /
install), and the app's Skill Hub pages call them over the SDK like the
Stacks tab already did. The Electron IPC bridge — the main-process
controller, its proxy warm-up, the preload namespace, platform.skillhub,
and the app-owned wire types — is deleted; nothing desktop-side remains.

That removes #363's off-host refusal rather than keeping it: the server
downloads the skill and installs it where the agent runs, so "This
project" is valid for every project the connected server knows — local,
WSL, sandbox, or remote — and "project" is resolved server-side from the
request's directory, never from a path the renderer sends. It also gives
the web app the Skill Hub: the sidebar entry is no longer gated on a
desktop capability. A newly installed skill is made visible to the model
by disposing the instance, as the stack install already does.

The sign-in offer on an auth failure stays, but only when the app's own
SSO writes the session the server will read — i.e. when this machine is
the server. Hub traffic now takes the sidecar's network path, which
honours the system trust store and HTTPS_PROXY-style settings but not a
proxy configured only through OS settings or a PAC file; the changelog
says so.

The plaintext netmind host in the detail page is base64'd like every
other one in shipped source.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P1MfYkJD3tQZRgqBKB4gig

* fix(skillhub): tolerate the hub's nulls so one card can't fail the whole page

The live hub sends unset columns as null (`metadata.publisher`,
`author.avatar`, …). The old desktop bridge never validated shapes, but
the server's response schema types those fields as optional strings, so
a single null card made the Skills tab 500 into "Something went wrong".

Nulls now become absent fields at the package's coercion seam, once, so
the CLI, the TUI, and the server's strict schema all see one total shape
(avatar keeps its null: the hub means "no picture" by it). The exerciser
fixture carries the same nulls so CI covers the class, and the browse
pages print the server's own message under the generic line instead of
hiding it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P1MfYkJD3tQZRgqBKB4gig

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
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.

1 participant