From 71e6b1874a4857c0edd5a78d775adc9849e3871b Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Tue, 23 Jun 2026 05:16:10 -0700 Subject: [PATCH] Studio: fall back to anonymous HF browsing on a malformed token (#6605) * Studio: fall back to anonymous HF browsing on a malformed token The Discover/Recommended feeds call the Hugging Face JS client (`listModels`/`listDatasets`) directly from the browser. That client throws `Your access token must start with 'hf_'` when handed a non-empty token that isn't a well-formed HF token, instead of falling back to anonymous access. A single bad value left in the HF token field (e.g. a placeholder someone typed) therefore takes down the entire discovery feed even though it works fine with no token at all. Add `hfApiToken()` to the HF token store, which returns the token only when it looks like a real `hf_...` credential and `undefined` otherwise, and route hub-page's four HF call sites through it. Malformed tokens now degrade to anonymous public browsing rather than erroring. The raw token is still stored and shown in the settings field unchanged. Co-Authored-By: Claude Opus 4.8 * Studio: trim hfApiToken comments Collapse the 11-line JSDoc to a 2-line note and drop the redundant call-site comment in hub-page. AST signature check confirms code is unchanged (comments only). Co-Authored-By: Claude Opus 4.8 --------- Co-authored-by: Claude Opus 4.8 --- studio/frontend/src/features/hub/hub-page.tsx | 14 +++++++++----- .../src/features/hub/stores/hf-token-store.ts | 8 ++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/studio/frontend/src/features/hub/hub-page.tsx b/studio/frontend/src/features/hub/hub-page.tsx index e379d7f2ee..b919e5f043 100644 --- a/studio/frontend/src/features/hub/hub-page.tsx +++ b/studio/frontend/src/features/hub/hub-page.tsx @@ -14,7 +14,10 @@ import { useHubInfiniteScroll } from "@/features/hub/hooks/use-hub-infinite-scro import { ggufVariantsMatch, modelIdsMatch } from "@/features/hub/lib/model-identity"; import { cn } from "@/lib/utils"; import { usePlatformStore } from "@/config/env"; -import { useHfTokenStore } from "@/features/hub/stores/hf-token-store"; +import { + hfApiToken, + useHfTokenStore, +} from "@/features/hub/stores/hf-token-store"; import { getInferenceStatus, isExternalModelId, @@ -551,6 +554,7 @@ export function ModelsPage() { const deferredDebouncedQuery = useDeferredValue(debouncedQuery); const hfToken = useHfTokenStore((s) => s.token); const debouncedHfToken = useDebouncedValue(hfToken, 500); + const apiHfToken = hfApiToken(debouncedHfToken); const deferredFormatFilter = useDeferredValue(formatFilter); const deferredCapabilityFilter = useDeferredValue(capabilityFilter); @@ -605,7 +609,7 @@ export function ModelsPage() { handleRetrySearch, } = useDiscoverSearch({ debouncedQuery, - accessToken: debouncedHfToken || undefined, + accessToken: apiHfToken, isDiscoverTab, isDatasetMode, sortBy: effectiveSort, @@ -619,7 +623,7 @@ export function ModelsPage() { channelId: isChannelListMode ? activeChannelId : null, results, isLoading, - accessToken: debouncedHfToken || undefined, + accessToken: apiHfToken, }); const { @@ -702,7 +706,7 @@ export function ModelsPage() { const listRows = filteredDiscoverRows; const hubFeed = useHubFeed({ - accessToken: debouncedHfToken || undefined, + accessToken: apiHfToken, online, enabled: isFeedMode, deviceType, @@ -908,7 +912,7 @@ export function ModelsPage() { filteredCachedRows, filteredLocalRows, results: selectionResults, - accessToken: debouncedHfToken || undefined, + accessToken: apiHfToken, online, }); diff --git a/studio/frontend/src/features/hub/stores/hf-token-store.ts b/studio/frontend/src/features/hub/stores/hf-token-store.ts index 31a7f0a4da..b1e2560f02 100644 --- a/studio/frontend/src/features/hub/stores/hf-token-store.ts +++ b/studio/frontend/src/features/hub/stores/hf-token-store.ts @@ -116,3 +116,11 @@ export const useHfTokenStore = create((set) => { export function getHfToken(): string { return useHfTokenStore.getState().token; } + +// HF's JS client throws on a non-empty token that isn't `hf_...` instead of +// browsing anonymously, so treat anything malformed as no token. +export function hfApiToken( + token: string | undefined | null, +): string | undefined { + return token && token.startsWith("hf_") ? token : undefined; +}