Skip to content

fix: retry curated skill installs on every startup#582

Merged
alchemistklk merged 1 commit intomainfrom
fix/curated-skills-retry-on-startup-v2
Mar 26, 2026
Merged

fix: retry curated skill installs on every startup#582
alchemistklk merged 1 commit intomainfrom
fix/curated-skills-retry-on-startup-v2

Conversation

@alchemistklk
Copy link
Copy Markdown
Contributor

@alchemistklk alchemistklk commented Mar 26, 2026

Relates to #612

What

Curated skills that failed ClawHub download during first launch were never retried on subsequent boots.

Why

initialize() (which enqueues curated skills) only ran when isFirstLaunch was true — i.e., when the skill ledger file didn't exist. Failed installs wrote no ledger record, so they'd be eligible for retry, but the code never re-checked after the first launch.

How

  • Remove isFirstLaunch gate — initialize() now runs on every non-CI startup
  • Add ledger check in copyStaticSkills so user-uninstalled static skills are not re-copied
  • Both operations are idempotent: copyStaticSkills skips when SKILL.md exists on disk OR slug is known in ledger; getCuratedSlugsToEnqueue filters against all known slugs
  • Fix dev mode OPENCLAW_SKILLS_DIR to use NEXU_HOME-scoped path for consistent dev/packaged behavior

Affected areas

  • apps/controller/src/services/skillhub-service.ts
  • apps/controller/src/services/skillhub/curated-skills.ts
  • apps/desktop/main/runtime/manifests.ts

Checklist

  • pnpm typecheck passes
  • pnpm test passes (199 tests, including new retry test)
  • pnpm lint passes

Summary by CodeRabbit

  • Improvements
    • Enhanced skill initialization to run consistently on every startup, ensuring reliable skill setup across launches.
    • Improved curated skills management to better track and prevent duplicate skill processing.
    • Refined skills directory path handling for development and production builds.

…stalls

Curated skills that failed ClawHub download during first launch were
never retried because initialize() only ran when the ledger didn't exist.
Now initialize() runs on every non-CI startup — both copyStaticSkills and
getCuratedSlugsToEnqueue are idempotent via ledger checks.

Also fixes dev mode skills dir to use NEXU_HOME-scoped path instead of
Electron userData, so dev and packaged app behavior are consistent.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 26, 2026

📝 Walkthrough

Walkthrough

The pull request removes the isFirstLaunch flag from SkillhubService, changing initialization to run on every non-CI startup instead of only on first launch. The copyStaticSkills function now checks the skill ledger before copying to avoid duplicates. The OpenClaw skills directory path handling is adjusted based on whether the application is packaged.

Changes

Cohort / File(s) Summary
Skillhub Service Core
apps/controller/src/services/skillhub-service.ts
Removed isFirstLaunch constructor parameter and flag; updated start() to call initialize() on every non-CI startup instead of conditionally on first launch.
Curated Skills Logic
apps/controller/src/services/skillhub/curated-skills.ts
Enhanced copyStaticSkills to pre-fetch known skill slugs from the ledger and skip copying when a slug already exists in the database, adding an extra control-flow branch before the file system check.
Tests
apps/controller/tests/skillhub-service.test.ts
Added import for writeFileSync and new test case verifying that start() enqueues curated skills even when the skill ledger already exists from a prior launch.
Desktop Runtime Configuration
apps/desktop/main/runtime/manifests.ts
Updated OpenClaw skills directory path handling in createRuntimeUnitManifests to use a runtime-scoped path when unpacked, while retaining original behavior for packaged builds.

Sequence Diagram(s)

sequenceDiagram
    participant App as App/Runtime
    participant Service as SkillhubService
    participant DB as SkillDB
    participant CuratedSkills as copyStaticSkills
    participant Queue as Install Queue

    Note over App,Queue: New Flow: Initialize on Every Startup

    App->>Service: start()
    activate Service
    Service->>Service: initialize()
    activate Service
    Service->>DB: Check existing ledger
    DB-->>Service: Ledger data
    Service->>CuratedSkills: copyStaticSkills()
    activate CuratedSkills
    CuratedSkills->>DB: Fetch all known slugs
    DB-->>CuratedSkills: Slug set
    CuratedSkills->>CuratedSkills: For each STATIC_SKILL_SLUG:<br/>Check if in ledger
    note right of CuratedSkills: If in ledger → Skip<br/>If not → Copy
    CuratedSkills-->>Service: Skipped/Copied results
    deactivate CuratedSkills
    Service->>Service: getCuratedSlugsToEnqueue()
    Service->>Queue: enqueue(slug, "managed")
    deactivate Service
    Queue-->>Service: Queued
    deactivate Service
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • lefarcen
  • PerishCode

Poem

🐰 No more first-launch flags to track,
Initialize each time, no looking back!
Ledger-aware skills, no dupes in sight,
Always-on startup—shining bright! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: removing the first-launch gate so curated skill installs are retried on every startup.
Description check ✅ Passed The description covers all required sections (What, Why, How, Affected areas) and includes completed checklist items verifying typecheck, test, and lint passes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/curated-skills-retry-on-startup-v2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
apps/desktop/main/runtime/manifests.ts (1)

237-244: Consolidate skills-dir resolution into one local constant.

The same packaged/dev path decision is duplicated in two places. Computing it once reduces drift risk.

♻️ Suggested refactor
-  ensureDir(
-    isPackaged
-      ? getOpenclawSkillsDir(userDataPath)
-      : path.resolve(
-          runtimeConfig.paths.nexuHome,
-          "runtime/openclaw/state/skills",
-        ),
-  );
+  const openclawSkillsDir = ensureDir(
+    isPackaged
+      ? getOpenclawSkillsDir(userDataPath)
+      : path.resolve(
+          runtimeConfig.paths.nexuHome,
+          "runtime/openclaw/state/skills",
+        ),
+  );
@@
-        OPENCLAW_SKILLS_DIR: isPackaged
-          ? getOpenclawSkillsDir(userDataPath)
-          : ensureDir(
-              path.resolve(
-                runtimeConfig.paths.nexuHome,
-                "runtime/openclaw/state/skills",
-              ),
-            ),
+        OPENCLAW_SKILLS_DIR: openclawSkillsDir,

Also applies to: 332-339

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/main/runtime/manifests.ts` around lines 237 - 244, Duplicate
logic deciding the skills directory path is used in multiple places; extract it
into a single local constant and reuse it where ensureDir(...) is called.
Introduce a const (e.g. skillsDir) computed once using the existing condition
with isPackaged ? getOpenclawSkillsDir(userDataPath) :
path.resolve(runtimeConfig.paths.nexuHome, "runtime/openclaw/state/skills"),
then replace both ensureDir(...) calls (and any other occurrences around the
ensureDir calls) with ensureDir(skillsDir) so the packaged/dev resolution is
centralized and cannot drift.
apps/controller/tests/skillhub-service.test.ts (1)

467-467: Optional: avoid implying a specific ledger file format in this test.

Since this test only needs the file to exist, writing an empty file is clearer than writing JSON into a .db path.

♻️ Suggested cleanup
-    writeFileSync(env.skillDbPath, JSON.stringify({ skills: [] }));
+    writeFileSync(env.skillDbPath, "");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/controller/tests/skillhub-service.test.ts` at line 467, The test
currently writes JSON to env.skillDbPath using
writeFileSync(JSON.stringify(...)) which implies a specific ledger file format;
change the test to simply create an empty file at env.skillDbPath (e.g., use
writeFileSync(env.skillDbPath, '') or equivalent) so the test only ensures the
file exists without asserting a JSON/database format; update the call site where
writeFileSync is used in the test to write an empty string instead of JSON.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/controller/tests/skillhub-service.test.ts`:
- Line 467: The test currently writes JSON to env.skillDbPath using
writeFileSync(JSON.stringify(...)) which implies a specific ledger file format;
change the test to simply create an empty file at env.skillDbPath (e.g., use
writeFileSync(env.skillDbPath, '') or equivalent) so the test only ensures the
file exists without asserting a JSON/database format; update the call site where
writeFileSync is used in the test to write an empty string instead of JSON.

In `@apps/desktop/main/runtime/manifests.ts`:
- Around line 237-244: Duplicate logic deciding the skills directory path is
used in multiple places; extract it into a single local constant and reuse it
where ensureDir(...) is called. Introduce a const (e.g. skillsDir) computed once
using the existing condition with isPackaged ?
getOpenclawSkillsDir(userDataPath) : path.resolve(runtimeConfig.paths.nexuHome,
"runtime/openclaw/state/skills"), then replace both ensureDir(...) calls (and
any other occurrences around the ensureDir calls) with ensureDir(skillsDir) so
the packaged/dev resolution is centralized and cannot drift.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1de28cdd-b261-4d7f-a7df-80ad7b0cece1

📥 Commits

Reviewing files that changed from the base of the PR and between cd19976 and 64452b2.

📒 Files selected for processing (4)
  • apps/controller/src/services/skillhub-service.ts
  • apps/controller/src/services/skillhub/curated-skills.ts
  • apps/controller/tests/skillhub-service.test.ts
  • apps/desktop/main/runtime/manifests.ts

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 64452b212c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/desktop/main/runtime/manifests.ts
@alchemistklk
Copy link
Copy Markdown
Contributor Author

/cr

@slack-code-review-channel
Copy link
Copy Markdown

✅ CR topic created in Feishu topic group Refly CR.

@alchemistklk alchemistklk merged commit db86246 into main Mar 26, 2026
8 checks passed
@lefarcen lefarcen mentioned this pull request Mar 30, 2026
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.

2 participants