Skip to content

Commit 974f991

Browse files
authored
Handle missing bundled skills catalog more gracefully (#213)
- 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
1 parent c46b1a3 commit 974f991

4 files changed

Lines changed: 53 additions & 1 deletion

File tree

apps/server/scripts/cli.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,16 @@ const buildCmd = Command.make(
140140
})`bun tsdown`,
141141
);
142142

143+
// Copy bundled skill catalog assets so import.meta.url resolution works at runtime.
144+
const sharedSkillsCatalog = path.join(repoRoot, "packages/shared/src/skills-catalog");
145+
const distSkillsCatalog = path.join(serverDir, "dist/skills-catalog");
146+
if (yield* fs.exists(sharedSkillsCatalog)) {
147+
yield* fs.copy(sharedSkillsCatalog, distSkillsCatalog);
148+
yield* Effect.log("[cli] Copied skills-catalog into dist/skills-catalog");
149+
} else {
150+
yield* Effect.logWarning("[cli] skills-catalog not found — skipping.");
151+
}
152+
143153
const webDist = path.join(repoRoot, "apps/web/dist");
144154
const clientTarget = path.join(serverDir, "dist/client");
145155

apps/server/src/skills/SkillService.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,17 @@ function toSkillEntry(entry: ReturnType<typeof listSkills>[number]) {
150150
}
151151

152152
const catalogEntries = listBundledSkills();
153-
ensureSystemSkillsInstalled();
153+
154+
// Deferred initialization — avoid crashing the module if bundled skill assets
155+
// are missing (e.g. the skills-catalog directory wasn't copied into dist/).
156+
try {
157+
ensureSystemSkillsInstalled();
158+
} catch (error) {
159+
console.warn(
160+
"[SkillService] Failed to bootstrap system skills — bundled skill assets may be missing:",
161+
error instanceof Error ? error.message : String(error),
162+
);
163+
}
154164

155165
export const SkillServiceLive = Layer.succeed(SkillService, {
156166
catalog: (input) =>

apps/web/src/components/skills/SkillsPage.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useEffect, useMemo, useState } from "react";
77
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
88
import { useNavigate } from "@tanstack/react-router";
99
import {
10+
AlertTriangleIcon,
1011
BookOpenIcon,
1112
CheckIcon,
1213
FileSpreadsheetIcon,
@@ -18,6 +19,7 @@ import {
1819
PlayIcon,
1920
PlusIcon,
2021
PlugIcon,
22+
RefreshCwIcon,
2123
SearchIcon,
2224
SparklesIcon,
2325
} from "lucide-react";
@@ -416,6 +418,9 @@ export function SkillsPage(props: {
416418
return (installedSkillsQuery.data?.skills ?? []).filter((skill) => !skill.system);
417419
}, [installedSkillsQuery.data?.skills]);
418420

421+
const hasQueryError = catalogQuery.isError || installedSkillsQuery.isError;
422+
const queryErrorMessage = catalogQuery.error?.message ?? installedSkillsQuery.error?.message;
423+
419424
const matchesSearch = (name: string, description: string, tags: readonly string[]) => {
420425
const query = searchValue.trim().toLowerCase();
421426
if (!query) return true;
@@ -501,6 +506,31 @@ export function SkillsPage(props: {
501506
</div>
502507
</div>
503508

509+
{hasQueryError ? (
510+
<div className="flex items-start gap-3 rounded-2xl border border-destructive/30 bg-destructive/5 p-4">
511+
<AlertTriangleIcon className="mt-0.5 size-5 shrink-0 text-destructive" />
512+
<div className="min-w-0 flex-1">
513+
<p className="font-medium text-sm text-foreground">
514+
Unable to load skills
515+
</p>
516+
<p className="mt-1 text-sm text-muted-foreground">
517+
{queryErrorMessage ?? "The skills service is unavailable. Check that the server is running."}
518+
</p>
519+
<Button
520+
variant="outline"
521+
size="sm"
522+
className="mt-3"
523+
onClick={() => {
524+
void queryClient.invalidateQueries({ queryKey: skillQueryKeys.all });
525+
}}
526+
>
527+
<RefreshCwIcon className="size-3.5" />
528+
Retry
529+
</Button>
530+
</div>
531+
</div>
532+
) : null}
533+
504534
<section className="space-y-4">
505535
<SectionHeader
506536
title="Recommended"

apps/web/src/lib/skillReactQuery.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export function skillCatalogQueryOptions(input: {
2626
enabled: input.enabled !== false,
2727
staleTime: input.staleTime ?? 30_000,
2828
placeholderData: EMPTY_SKILL_CATALOG_RESULT,
29+
retry: 2,
2930
});
3031
}
3132

@@ -44,6 +45,7 @@ export function skillListQueryOptions(input: {
4445
enabled: input.enabled !== false,
4546
staleTime: input.staleTime ?? 30_000,
4647
placeholderData: EMPTY_SKILL_LIST_RESULT,
48+
retry: 2,
4749
});
4850
}
4951

0 commit comments

Comments
 (0)