Problem
SkillsSection.handleToggleEnabled and ClawHubTab.handleInstall both use the pattern:
setXxx((prev) => new Set(prev).add(key)) → await window.clawwork.xxx(...) → setXxx((prev) => remove key).
The cleanup setXxx sits after the await, outside any try/finally. If the IPC call throws, the skill / slug is never removed from the "toggling" / "installing" set and its Power button / Install button stays spinning forever. The user has no way to retry without refreshing the entire section.
Location
File: packages/desktop/src/renderer/layouts/Settings/sections/SkillsSection.tsx:710-732 (toggle) and 566-583 (install)
// handleToggleEnabled
setTogglingKeys((prev) => new Set(prev).add(skill.skillKey));
const newEnabled = skill.disabled;
const res = await window.clawwork.updateSkill(...); // throw → stuck
if (res.ok) { ... } else { ... }
setTogglingKeys((prev) => { const next = new Set(prev); next.delete(skill.skillKey); return next; });
// handleInstall
setInstallingSlugs((prev) => new Set(prev).add(slug));
const res = await window.clawwork.installSkill(gatewayId, { source: 'clawhub', slug }); // throw → stuck
// ... branches ...
setInstallingSlugs((prev) => { const next = new Set(prev); next.delete(slug); return next; });
Fix Approach
Wrap the IPC call in try/finally so the set is always cleaned up:
setTogglingKeys((prev) => new Set(prev).add(skill.skillKey));
try {
const res = await window.clawwork.updateSkill(...);
if (res.ok) { ... } else { toast.error(...); }
} catch (err) {
console.error('[SkillsSection] toggle failed:', err);
toast.error(t('settings.skillUpdateFailed'));
} finally {
setTogglingKeys((prev) => {
const next = new Set(prev);
next.delete(skill.skillKey);
return next;
});
}
Apply the same pattern to handleInstall.
Verification
- Run
pnpm check — must pass.
- Manual: simulate IPC throw during a skill toggle — the button must return to its steady state and a toast should surface the error.
Context
- WG: UI & Design System
- Priority: Low (good first issue)
- Estimated effort: 15-20 minutes
Problem
SkillsSection.handleToggleEnabledandClawHubTab.handleInstallboth use the pattern:setXxx((prev) => new Set(prev).add(key))→await window.clawwork.xxx(...)→setXxx((prev) => remove key).The cleanup
setXxxsits after theawait, outside anytry/finally. If the IPC call throws, the skill / slug is never removed from the "toggling" / "installing" set and its Power button / Install button stays spinning forever. The user has no way to retry without refreshing the entire section.Location
File:
packages/desktop/src/renderer/layouts/Settings/sections/SkillsSection.tsx:710-732(toggle) and566-583(install)Fix Approach
Wrap the IPC call in
try/finallyso the set is always cleaned up:Apply the same pattern to
handleInstall.Verification
pnpm check— must pass.Context