From df7168ee707bdfda8494af211734b34ea38f13b9 Mon Sep 17 00:00:00 2001 From: Val Alexander Date: Thu, 2 Apr 2026 16:10:19 -0500 Subject: [PATCH] Handle missing bundled skills catalog more gracefully - Copy bundled skill catalog into server dist during build - Guard system skill bootstrap when assets are absent - Show retryable skill load errors in the web UI and retry queries --- apps/server/scripts/cli.ts | 10 +++++++ apps/server/src/skills/SkillService.ts | 12 +++++++- apps/web/src/components/skills/SkillsPage.tsx | 30 +++++++++++++++++++ apps/web/src/lib/skillReactQuery.ts | 2 ++ 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/apps/server/scripts/cli.ts b/apps/server/scripts/cli.ts index a3406354a..abc83c07a 100644 --- a/apps/server/scripts/cli.ts +++ b/apps/server/scripts/cli.ts @@ -140,6 +140,16 @@ const buildCmd = Command.make( })`bun tsdown`, ); + // Copy bundled skill catalog assets so import.meta.url resolution works at runtime. + const sharedSkillsCatalog = path.join(repoRoot, "packages/shared/src/skills-catalog"); + const distSkillsCatalog = path.join(serverDir, "dist/skills-catalog"); + if (yield* fs.exists(sharedSkillsCatalog)) { + yield* fs.copy(sharedSkillsCatalog, distSkillsCatalog); + yield* Effect.log("[cli] Copied skills-catalog into dist/skills-catalog"); + } else { + yield* Effect.logWarning("[cli] skills-catalog not found — skipping."); + } + const webDist = path.join(repoRoot, "apps/web/dist"); const clientTarget = path.join(serverDir, "dist/client"); diff --git a/apps/server/src/skills/SkillService.ts b/apps/server/src/skills/SkillService.ts index ebafae25a..b32fdfdf1 100644 --- a/apps/server/src/skills/SkillService.ts +++ b/apps/server/src/skills/SkillService.ts @@ -150,7 +150,17 @@ function toSkillEntry(entry: ReturnType[number]) { } const catalogEntries = listBundledSkills(); -ensureSystemSkillsInstalled(); + +// Deferred initialization — avoid crashing the module if bundled skill assets +// are missing (e.g. the skills-catalog directory wasn't copied into dist/). +try { + ensureSystemSkillsInstalled(); +} catch (error) { + console.warn( + "[SkillService] Failed to bootstrap system skills — bundled skill assets may be missing:", + error instanceof Error ? error.message : String(error), + ); +} export const SkillServiceLive = Layer.succeed(SkillService, { catalog: (input) => diff --git a/apps/web/src/components/skills/SkillsPage.tsx b/apps/web/src/components/skills/SkillsPage.tsx index 0e3ffda65..cc70f1389 100644 --- a/apps/web/src/components/skills/SkillsPage.tsx +++ b/apps/web/src/components/skills/SkillsPage.tsx @@ -7,6 +7,7 @@ import { useEffect, useMemo, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useNavigate } from "@tanstack/react-router"; import { + AlertTriangleIcon, BookOpenIcon, CheckIcon, FileSpreadsheetIcon, @@ -18,6 +19,7 @@ import { PlayIcon, PlusIcon, PlugIcon, + RefreshCwIcon, SearchIcon, SparklesIcon, } from "lucide-react"; @@ -416,6 +418,9 @@ export function SkillsPage(props: { return (installedSkillsQuery.data?.skills ?? []).filter((skill) => !skill.system); }, [installedSkillsQuery.data?.skills]); + const hasQueryError = catalogQuery.isError || installedSkillsQuery.isError; + const queryErrorMessage = catalogQuery.error?.message ?? installedSkillsQuery.error?.message; + const matchesSearch = (name: string, description: string, tags: readonly string[]) => { const query = searchValue.trim().toLowerCase(); if (!query) return true; @@ -501,6 +506,31 @@ export function SkillsPage(props: { + {hasQueryError ? ( +
+ +
+

+ Unable to load skills +

+

+ {queryErrorMessage ?? "The skills service is unavailable. Check that the server is running."} +

+ +
+
+ ) : null} +