Skip to content

feat: complete WorkOS skill install + refresh loop with doctor --fix#130

Merged
nicknisi merged 10 commits into
mainfrom
nicknisi/cus-feedback
Apr 26, 2026
Merged

feat: complete WorkOS skill install + refresh loop with doctor --fix#130
nicknisi merged 10 commits into
mainfrom
nicknisi/cus-feedback

Conversation

@nicknisi
Copy link
Copy Markdown
Member

@nicknisi nicknisi commented Apr 26, 2026

Summary

Closes Batch A of the cus-feedback-fixes initiative — the WorkOS skill content the CLI auto-installs is now correctly delivered end-to-end (SKILL.md plus the entire references/ tree), self-healing on stale state via workos doctor --fix, and refreshed on every workos auth login. This is the surface that addresses the customer friction case where claude diagnosed a CLI gap, sent users to fabricated commands or non-existent dashboard click-paths, and had no way to recover.

Builds on top of the existing branch (which surfaced auto-install + added a doctor staleness warning) by replacing the install machinery, extracting a refresh primitive, wiring the post-login hook, and adding an explicit --fix flag on doctor.

What's in this PR

Existing (already in the branch)

  • autoInstallSkills runs after every workos install, copies bundled @workos/skills content to every detected coding agent, prints a one-line summary in TTY mode, suppresses in JSON mode, writes a .workos-skill-version marker per agent.
  • workos doctor checkSkills compares each agent's marker against the bundled version and emits SKILLS_OUTDATED when they drift.

New: recursive prune-replace install (Phase 2)

installSkill was a single copyFile(SKILL.md) — it shipped the router but not the topic files the router instructs the agent to Read. Rewritten to:

  • mkdtemp + recursive cp + sibling .workos.bak-<skill>-<rand> backup-rename + rollback if the temp→target rename fails. Operation is effectively atomic per skill.
  • Best-effort backup cleanup post-success — install does NOT fail if the backup remains; the next run's cleanupStaleOrphans reaps it after 1h.
  • cleanupStaleOrphans with a 1-hour mtime cutoff: orphans from crashed prior runs are removed, but fresh siblings from a concurrent run are preserved (no race-deletion).
  • Failure modes covered: mid-rename rollback, cp-failure tempDir cleanup, EXDEV-by-construction (sibling pattern), concurrent-run safety.

New: refresh primitive (Phase 2)

refreshWorkOSSkills(opts) is a reusable primitive returning RefreshResult with perAgentBefore / perAgentAfter keyed by agent.name. Both autoInstallSkills (best-effort hook) and doctor --fix call this — no duplicate copy logic. autoInstallSkills becomes a thin back-compat wrapper.

New: auth-login skill install hook (Phase 2)

installSkillsAfterLogin helper wired into runLogin after provisionStagingEnvironment. Wraps autoInstallSkills in its own try/catch so a skill-install failure NEVER fails login itself. JSON-mode-aware. Extracted as a separate exported function so it's unit-testable without standing up the device-auth polling loop.

New: workos doctor --fix (Phase 3)

workos doctor (no flag) keeps its warn-only behavior. --fix is the explicit opt-in for mutating state from a diagnostic command. When set and at least one agent has a stale or missing marker:

  • Refresh runs scoped to a hardcoded FIXABLE_SKILLS = ['workos', 'workos-widgets'] allowlist — NOT derived from discoverSkills(). A future bundled skill needs explicit opt-in here before doctor will write to its target directory.
  • After refresh, checkSkills() is re-read and detectIssues runs against the post-refresh state — no lingering SKILLS_OUTDATED warning after the refresh has already fixed it.
  • Human-mode renders one line per agent: ✓ Updated WorkOS skills for {Agent}: {before} → {after} (null → (none)).
  • JSON mode automatically picks up skillsRefresh via JSON.stringify(report).

Bump: @workos/skills 0.2.4 → 0.4.0

@workos/skills@0.4.0 published today (workos/skills#26 + the release-please release PR). Pulling the new content in so the recursive install machinery actually delivers the new guardrails:

  • references/workos-cli-upgrade.md — router-style content for "user is on outdated workos" with explicit "do not fabricate the latest version" guardrails (instructs the agent to run npm view workos version).
  • SKILL.md Rule 6 sub-case routing outdated-CLI symptoms to the new reference.
  • workos-management.md "Detecting and recommending CLI upgrades" subsection.

Test plan

  • pnpm typecheck — clean
  • pnpm build — clean
  • Targeted: pnpm test src/commands/install-skill.spec.ts src/commands/login.spec.ts src/doctor — 183 tests pass
  • Full suite: pnpm test — 1611 tests across 124 files
  • Local: pnpm install resolves @workos/skills@0.4.0; node_modules/@workos/skills/plugins/workos/skills/workos/references/workos-cli-upgrade.md is present
  • New install-skill tests cover: recursive copy of references/, prune-replace (planted stale file gone after re-install), rollback on simulated rename failure, cp-failure tempDir cleanup, mtime cutoff for orphan cleanup (both prefixes, both directions), peer-skill safety
  • New login.spec tests cover: hook helper invokes autoInstallSkills, returns successfully when installer throws, JSON-mode skip, singular/plural copy
  • New skills-fix.spec.ts covers: --fix=false no-op, --fix=true no-op when no skills info / when nothing stale, allowlist passed exactly to refreshWorkOSSkills, success path returns skillsRefresh + post-refresh re-read, refresh-null preserves original skills, integration: planted third-party-skill and hypothetical workos-future-skill files at the agent target are byte-identical after --fix

Sequencing notes

  • feat(workos): add CLI upgrade-path topic and tarball smoke test skills#26 was the prerequisite (added the new content + tarball smoke test gate). Merged + published as @workos/skills@0.4.0.
  • Phases 4 (installer event handlers) and 5 (binary shadowing detection) are independent of this PR and can ship next.
  • Phase 6 / 7 (topic-help system via getUnsupportedOperations() named export + workos help command) are Batch B per the contract — can defer to a follow-up sprint.

Summary by CodeRabbit

  • New Features

    • CLI gains a --fix option to auto-refresh WorkOS skills; auto-install runs after login and install now reports installed skill counts and agents.
    • New refresh operation returns before/after marker info when installs occur.
  • Bug Fixes

    • Skill installs are now atomic with rollback and better cleanup of stale temp/backup dirs.
    • Doctor now detects outdated or missing skill versions.
  • Improvements

    • Updated WorkOS skills dependency to v0.4.0
  • Tests

    • Expanded test coverage for install, refresh, doctor checks, and login flows.

autoInstallSkills now returns a summary describing which skills were
installed to which agents. handleInstall prints a clack info line in
TTY mode so users know their coding agent has up-to-date WorkOS
guidance. Suppressed in JSON mode.

Also writes a per-agent .workos-skill-version marker alongside
installed skills so downstream checks can detect staleness.
Compare each detected coding agent's .workos-skill-version marker
against the bundled @workos/skills version. Emit a SKILLS_OUTDATED
warning with a remediation pointing users at `workos skills install`.

Returns null when no agent has WorkOS skills installed, so doctor
stays quiet for users who never installed through the CLI.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Installer and doctor flows now detect, atomically install, and manage bundled WorkOS skills. Installs use temp→backup→rename with rollback and orphan cleanup; auto-install returns structured results or null. Doctor can inspect marker staleness and optionally refresh allowlisted skills, reporting before/after marker states.

Changes

Cohort / File(s) Summary
Install implementation & API
src/commands/install-skill.ts, src/commands/install-skill.spec.ts
Implements recursive skill install using temp-dir → rename with backup rollback and stale-orphan cleanup; adds SKILL_VERSION_MARKER_FILENAME, getBundledSkillsVersion, refreshWorkOSSkills, AutoInstallResult/Refresh* types; autoInstallSkills() → `AutoInstallResult
Install command wiring & tests
src/commands/install.ts, src/commands/install.spec.ts
Preserves autoInstallSkills() result and prints a one-line human-mode summary when non-null and not JSON mode. Tests updated to expect null or structured return and to assert TTY vs JSON logging.
Login integration & tests
src/commands/login.ts, src/commands/login.spec.ts
Adds installSkillsAfterLogin() with 30s timeout that best-effort calls autoInstallSkills() post-login; logs human-mode summary unless JSON. Tests verify suppression, pluralization, timeout, and null handling.
Doctor checks, types & tests
src/doctor/checks/skills.ts, src/doctor/checks/skills.spec.ts, src/doctor/types.ts
Adds checkSkills(home?) reporting bundledVersion and per-agent SkillAgentStatus (`installedVersion
Doctor orchestration, CLI flag & gating
src/doctor/index.ts, src/commands/doctor.ts, src/bin.ts, src/utils/help-json.ts
runDoctor includes checkSkills() output in report, and when --fix is set triggers maybeRefreshSkills() which calls refreshWorkOSSkills scoped by FIXABLE_SKILLS; report gains skillsRefresh. CLI/JSON help and command args add --fix.
Doctor issue detection & output
src/doctor/issues.ts, src/doctor/output.ts
Adds SKILLS_OUTDATED issue when any agent is stale; renderer prints Skills section showing per-agent before→after marker versions when skillsRefresh exists.
Refresh gating & integration tests
src/doctor/checks/skills-fix.spec.ts
Adds tests for maybeRefreshSkills covering fix gating, allowlist scoping (FIXABLE_SKILLS), skillsRefresh shape, and an integration-style test ensuring only allowlisted skill dirs are modified.
Various tests updated
src/commands/install-skill.spec.ts, src/commands/install.spec.ts, src/commands/login.spec.ts, src/doctor/checks/skills.spec.ts, src/doctor/checks/skills-fix.spec.ts
Expanded coverage for new return semantics (null), TTY vs JSON logging, install/refresh edge cases (rename/cp failures, rollback, orphan cleanup), and marker write/read behaviors.
Dependency bump
package.json
Updates @workos/skills dependency from 0.2.40.4.0.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.37% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a complete WorkOS skill install and refresh loop accessible via the new doctor --fix command, which aligns with the substantial changes across install, doctor, and login flows.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch nicknisi/cus-feedback

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

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 26, 2026

Greptile Summary

This PR completes the WorkOS skill install/refresh loop: recursive prune-replace install (installSkill now uses mkdtemp+cp+atomic rename with rollback), a shared refreshWorkOSSkills primitive used by both autoInstallSkills and doctor --fix, a --fix flag on workos doctor scoped to the FIXABLE_SKILLS allowlist, and a post-login skill install hook. The @workos/skills dependency is bumped to 0.4.0.

  • P1 (output.ts): report.skillsRefresh.before/.after are populated for every detected agent (including those where all installs failed), so the ✓ Updated line is printed for agents with no actual change — e.g., ✓ Updated WorkOS skills for Codex: (none) → (none). The loop should filter to slugs where before !== after."

Confidence Score: 4/5

Safe to merge after fixing the output.ts agent-filtering bug; all other logic is well-tested and architecturally sound.

One confirmed P1 in output.ts (success checkmark printed for failed-install agents) and one P2 in install-skill.ts (orphaned backup dir not cleaned in the outer catch on double-rename failure). No security concerns. Test coverage is thorough across all new code paths.

src/doctor/output.ts (P1 agent filtering), src/commands/install-skill.ts (P2 backupDir cleanup in catch)

Important Files Changed

Filename Overview
src/commands/install-skill.ts Core rewrite: single-file copy replaced with recursive prune-replace install using mkdtemp+cp+rename+rollback; new refreshWorkOSSkills primitive; backupDir not cleaned in outer catch on double-rename failure (P2)
src/doctor/output.ts Adds --fix skills refresh rendering; iterates over all agents in perAgentBefore including failed installs, printing ✓ for agents where before===after (P1)
src/doctor/checks/skills.ts New file: checkSkills() with semver-aware staleness detection; correctly gates on marker/workos/workos-widgets presence to avoid writing onto unrelated agents
src/doctor/index.ts Adds maybeRefreshSkills and FIXABLE_SKILLS allowlist; wires --fix into runDoctor before earlyIssues so post-refresh state flows to all downstream consumers
src/commands/login.ts Adds installSkillsAfterLogin helper with 30s timeout+unref; correctly isolated in try/catch so login success is never blocked by skill install failure
src/doctor/types.ts Adds SkillsInfo, SkillAgentStatus, SkillsRefreshResult types and extends DoctorReport/DoctorOptions; clean additions
src/doctor/issues.ts Adds SKILLS_OUTDATED issue emission when bundledVersion is known and stale agents present; correctly guarded behind bundledVersion check
src/commands/install.ts Adds TTY-mode success message after autoInstallSkills; correctly suppressed in JSON mode and when result is null

Sequence Diagram

sequenceDiagram
    participant CLI
    participant installSkill
    participant refreshWorkOSSkills
    participant checkSkills
    participant maybeRefreshSkills

    Note over CLI,maybeRefreshSkills: workos auth login / workos install
    CLI->>refreshWorkOSSkills: autoInstallSkills()
    refreshWorkOSSkills->>refreshWorkOSSkills: detectAgents() + discoverSkills()
    loop each agent x skill
        refreshWorkOSSkills->>installSkill: installSkill(src, skill, agent)
        installSkill->>installSkill: mkdtemp → cp → rename(target→bak) → rename(tmp→target)
        installSkill-->>refreshWorkOSSkills: {success, error}
    end
    refreshWorkOSSkills->>refreshWorkOSSkills: writeAgentSkillMarker (per succeeded agent)
    refreshWorkOSSkills-->>CLI: RefreshResult (perAgentBefore/After)

    Note over CLI,maybeRefreshSkills: workos doctor --fix
    CLI->>checkSkills: checkSkills()
    checkSkills-->>CLI: SkillsInfo (stale agents)
    CLI->>maybeRefreshSkills: maybeRefreshSkills({fix:true}, skills)
    maybeRefreshSkills->>refreshWorkOSSkills: refreshWorkOSSkills({skills: FIXABLE_SKILLS})
    refreshWorkOSSkills-->>maybeRefreshSkills: RefreshResult
    maybeRefreshSkills->>checkSkills: checkSkills() re-read post-refresh
    checkSkills-->>maybeRefreshSkills: updated SkillsInfo
    maybeRefreshSkills-->>CLI: {skillsRefresh, skills}
    CLI->>CLI: formatReport() → render before→after per agent
Loading

Reviews (7): Last reviewed commit: "docs(readme): mention auth-login skill h..." | Re-trigger Greptile

Comment thread src/commands/install-skill.ts Outdated
Comment thread src/commands/install.spec.ts
Comment thread src/doctor/checks/skills.ts Outdated
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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/install-skill.ts`:
- Around line 206-230: The code returns the full discovered skills list and
writes the SKILL_VERSION_MARKER_FILENAME for any agent with at least one
successful install, which can over-report installs; change the install loop in
the block using targetAgents/installSkill so that for each agent you collect the
subset of successfully installed skill names (e.g., per-agent installedSkills
array), only add the agent to succeededAgents when installedSkills.length > 0,
include those installed skill names in the final AutoInstallResult.skills (or
build a map/unique list of actually installed skills instead of using the
original skills array), and only write the .workos-skill-version marker for an
agent when installedSkills.length === skills.length (i.e., all requested skills
succeeded) to avoid marking partially-updated agents as up-to-date.
- Around line 17-23: The getBundledSkillsVersion function currently uses
synchronous file I/O (readFileSync); change it to an async function (export
async function getBundledSkillsVersion(...): Promise<string | null>) and replace
readFileSync/JSON.parse with await fs.promises.readFile (or import { promises as
fs } and use fs.readFile) and JSON.parse inside the try/catch, returning the
version or null on error; also update the caller autoInstallSkills to await the
new async getBundledSkillsVersion (e.g., const version = await
getBundledSkillsVersion(skillsDir)) and adjust any surrounding code to handle
the returned Promise.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6060ef64-104b-41b1-8bb8-b9aee2235055

📥 Commits

Reviewing files that changed from the base of the PR and between 9728bd7 and 3476e85.

📒 Files selected for processing (9)
  • src/commands/install-skill.spec.ts
  • src/commands/install-skill.ts
  • src/commands/install.spec.ts
  • src/commands/install.ts
  • src/doctor/checks/skills.spec.ts
  • src/doctor/checks/skills.ts
  • src/doctor/index.ts
  • src/doctor/issues.ts
  • src/doctor/types.ts

Comment thread src/commands/install-skill.ts Outdated
Comment thread src/commands/install-skill.ts Outdated
Phase 2 of cus-feedback-fixes. Closes the gap where `workos install`
shipped only `SKILL.md` and not the `references/` tree the WorkOS
skill router instructs the agent to Read. Without those reference
files, the auto-installed skill was functionally empty.

* Rewrite `installSkill` from a single `copyFile(SKILL.md)` to a
  recursive prune-replace of the whole `<skillsDir>/<skillName>/` tree
  using `mkdtemp` + `cp { recursive: true }` + sibling `.workos.bak-`
  backup-rename. Rolls back to the original target if the temp→target
  rename fails mid-flight; tempDir is cleaned up on copy failure.
  Backup cleanup is best-effort post-success — the install does not
  fail if the backup remains, and `cleanupStaleOrphans` will reap it
  on the next run after 1h.

* Add `cleanupStaleOrphans` with a 1-hour mtime cutoff (constant
  `ORPHAN_STALE_MS`). Concurrent runs that leave fresh `.tmp-*` /
  `.bak-*` siblings are never touched — the cutoff guarantees we
  only nuke orphans from runs that crashed long ago.

* Extract `refreshWorkOSSkills(opts)` as a reusable primitive returning
  `RefreshResult` with `perAgentBefore` / `perAgentAfter` keyed by
  `agent.name`. Both `autoInstallSkills` (best-effort hook) and
  doctor `--fix` (Phase 3) will call this — no duplicate copy logic.
  `autoInstallSkills` becomes a thin back-compat wrapper that returns
  the existing `AutoInstallResult` shape (`install.ts` consumers stay
  unchanged).

* Add `installSkillsAfterLogin` helper in `login.ts`, wired in after
  `provisionStagingEnvironment`. Wraps `autoInstallSkills` in its own
  try/catch so a skill install failure NEVER fails login itself.
  Skips logging in JSON mode, mirrors install.ts's success copy.
  Extracted as a separate exported function so it's unit-testable
  without standing up the device-auth polling loop.

* Tests:
  - `installSkill` copies `references/` subdirectory
  - planted stale `references/workos-stale.md` is gone after re-install
  - simulated rename failure restores original target from backup
  - copy failure cleans up tempDir (no leftover `.workos.tmp-*`)
  - `cleanupStaleOrphans` removes >1h orphans of both prefixes,
    preserves <1h orphans of both prefixes
  - peer skill directories (different prefix) are never touched
  - `refreshWorkOSSkills` reports per-agent before/after marker state,
    respects `writeMarker: false`, filters by `skills` and `agents`
  - `installSkillsAfterLogin` calls `autoInstallSkills`, returns
    successfully when installer throws, JSON-mode-aware, singular
    vs plural copy

NOT INCLUDED in this commit (intentional — see open items):
- `package.json` bump from `@workos/skills@0.2.4` to `0.4.0`. Phase 1
  (the skills-repo release providing the new content) is committed
  but not yet published to npm. The bump becomes a one-line follow-up
  once `0.4.0` is on the registry.
Phase 3 of cus-feedback-fixes. `workos doctor` (no flag) keeps its
warn-only behavior — `--fix` is the explicit opt-in for mutating
state from a diagnostic command. When the user runs `workos doctor
--fix` and at least one agent reports a stale or missing version
marker, refresh is scoped to a hardcoded allowlist of bundled WorkOS
skills (`workos`, `workos-widgets`) and a per-agent before/after
summary is rendered.

* `FIXABLE_SKILLS` is hardcoded `['workos', 'workos-widgets']` —
  NOT derived from `discoverSkills()`. A future bundled skill needs
  an explicit opt-in here before doctor `--fix` will write to its
  target directory. This is the contract's promise that `--fix`
  only ever touches `workos/` and `workos-widgets/`.

* `maybeRefreshSkills(options, skills)` extracted as a testable
  helper. Calls Phase 2's `refreshWorkOSSkills` with the allowlist
  via the `skills` filter, then re-runs `checkSkills()` so issue
  detection sees the post-refresh marker state. Without the re-run,
  `detectIssues` would still report `SKILLS_OUTDATED` immediately
  after fixing it.

* Renderer in `output.ts` adds a "Skills" section that prints one
  line per agent: `✓ Updated WorkOS skills for {DisplayName}: {before}
  → {after}` (treating `null` as `(none)`). JSON mode picks up
  `skillsRefresh` automatically via `JSON.stringify(report)`.

* Wired through `src/bin.ts` (yargs `--fix` boolean), `src/commands/
  doctor.ts` (DoctorArgs pass-through), and `src/doctor/types.ts`
  (`fix?: boolean` on DoctorOptions; `SkillsRefreshResult` interface
  with before/after keyed by agent.name; `skillsRefresh?` on
  DoctorReport).

* Tests in `src/doctor/checks/skills-fix.spec.ts`:
  - `--fix=false` does NOT call refresh even with stale skills
  - `--fix=true` is no-op when no skills info / when nothing is stale
  - allowlist passed to refreshWorkOSSkills exactly matches FIXABLE_SKILLS
  - success path returns skillsRefresh + post-refresh re-read of skills
  - refresh-returns-null preserves original skills (no skillsRefresh)
  - integration test against real refreshWorkOSSkills: planted
    third-party-skill and hypothetical workos-future-skill files
    at the agent target are byte-identical after `--fix` runs

Open Item from spec deferred: whether `--fix` should also fix
non-skill issues in the future. Out of scope here.
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.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/doctor/index.ts (1)

81-125: ⚠️ Potential issue | 🟠 Major

--fix still feeds stale skills into AI analysis.

earlyIssues and checkAiAnalysis() run before maybeRefreshSkills(), so a successful refresh can clear report.issues while report.aiAnalysis still contains pre-fix stale-skill findings or remediation. Move the refresh ahead of earlyIssues, or recompute the AI input after the refresh.

🔁 Reorder the refresh so AI sees post-fix state
   let skills = checkSkills() ?? undefined;
+  const refreshOutcome = await maybeRefreshSkills(options, skills);
+  const skillsRefresh = refreshOutcome.skillsRefresh;
+  skills = refreshOutcome.skills;
 
   // Dashboard settings + auth patterns + AI analysis (parallel, all need sdk/framework results)
   // AI analysis also receives early issues as context to avoid duplication
   const earlyIssues = detectIssues({
@@
-  // `--fix`: refresh stale WorkOS skills BEFORE issue detection so the report
-  // reflects the post-refresh state (no lingering SKILLS_OUTDATED warning
-  // after the refresh has already fixed it).
-  const refreshOutcome = await maybeRefreshSkills(options, skills);
-  const skillsRefresh = refreshOutcome.skillsRefresh;
-  skills = refreshOutcome.skills;
-
   // Build partial report
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/doctor/index.ts` around lines 81 - 125, Move the skills refresh so AI
receives post-fix state: call maybeRefreshSkills(...) before computing
earlyIssues and invoking checkAiAnalysis(...), or after refreshing update the
skills variable and recompute earlyIssues and the payload passed to
checkAiAnalysis; specifically adjust the sequence around skills,
maybeRefreshSkills, detectIssues (earlyIssues) and checkAiAnalysis so
checkAiAnalysis receives the refreshed skills (references: maybeRefreshSkills,
detectIssues, checkAiAnalysis, skills, skillsRefresh).
♻️ Duplicate comments (1)
src/commands/install-skill.ts (1)

305-336: ⚠️ Potential issue | 🟠 Major

Don't mark partially refreshed agents as current.

agentSucceeded flips to true after the first successful copy, so a single failed skill still writes the version marker and returns the full requested skills set. That makes doctor treat partially updated agents as fresh and overstates what was installed.

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

In `@src/commands/install-skill.ts` around lines 305 - 336, The loop currently
sets agentSucceeded true after the first successful install so partially-updated
agents get marked current and the full requested skills list is returned; change
the logic in the block that iterates detected agents to track per-agent
per-skill success (call installSkill for each skill, record each result), set
agentSucceeded to true only if every installSkill returned result.success (or
equivalently if there are no failures), write the SKILL_VERSION_MARKER_FILENAME
via writeFile only when agentSucceeded is true, and adjust the returned payload
so it does not advertise the full requested skills set for an agent that had
partial failures (use the actual successful installs rather than the original
skills array); reference functions/variables installSkill, agentSucceeded,
succeededAgents, perAgentBefore, perAgentAfter, writeFile, and
SKILL_VERSION_MARKER_FILENAME when updating the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/install-skill.spec.ts`:
- Around line 389-413: The test never forces a resolvable bundled version, so it
can pass without exercising the marker-write success path; modify the spec to
make the bundled version deterministic (e.g., create a package.json with a
"version" in the temp skillsDir/artifact or stub getBundledSkillsVersion) before
calling autoInstallSkills(), then assert that SKILL_VERSION_MARKER_FILENAME in
join(homeDir, '.claude/skills') exists and its contents equal result.version;
target symbols: SKILL_VERSION_MARKER_FILENAME, autoInstallSkills, skillsDir,
homeDir, and the test block that builds skill-a so the marker-write branch is
provably executed.

In `@src/commands/install-skill.ts`:
- Around line 290-337: The explicit install flow should reuse
refreshWorkOSSkills so markers get written; change runInstallSkill (and the
other direct-install path that currently loops 339-349) to call
refreshWorkOSSkills with the requested skills and detected agents (e.g.
refreshWorkOSSkills({ skills: requestedSkills, agents: detectedAgents,
writeMarker: true })) instead of performing the old per-skill install loop using
installSkill directly, ensuring the returned RefreshResult is propagated so
.workos-skill-version markers are created consistently.
- Line 127: Several synchronous fs calls violate the async-only guideline:
replace existsSync(targetDir) in installSkill with await
stat(targetDir).catch(() => null) and use that truthiness for targetExisted;
similarly change existsSync(parent) in cleanupStaleOrphans to await
stat(parent).catch(() => null); convert readSkillVersionMarker into an async
function that uses readFile from fs/promises (instead of readFileSync) and
update its callers in refreshWorkOSSkills to await the new async function; and
in discoverSkills replace existsSync(join(skillsDir, e.name, 'SKILL.md')) with
an await stat(...).catch(() => null) check. Ensure to import stat/readFile from
'fs/promises' and preserve existing error handling/returns when stat/readFile
return null.

In `@src/commands/login.ts`:
- Around line 86-95: The post-login hook installSkillsAfterLogin currently
awaits autoInstallSkills(), which can hang and block runLogin(); change this so
skill installation cannot stall login by either (a) running autoInstallSkills()
without awaiting its Promise (fire-and-forget) or (b) wrapping the await in a
bounded timeout (e.g., Promise.race with a timeout) so installSkillsAfterLogin
returns promptly; update references in installSkillsAfterLogin and any callers
so credentials/success UI are not dependent on autoInstallSkills() completing.

---

Outside diff comments:
In `@src/doctor/index.ts`:
- Around line 81-125: Move the skills refresh so AI receives post-fix state:
call maybeRefreshSkills(...) before computing earlyIssues and invoking
checkAiAnalysis(...), or after refreshing update the skills variable and
recompute earlyIssues and the payload passed to checkAiAnalysis; specifically
adjust the sequence around skills, maybeRefreshSkills, detectIssues
(earlyIssues) and checkAiAnalysis so checkAiAnalysis receives the refreshed
skills (references: maybeRefreshSkills, detectIssues, checkAiAnalysis, skills,
skillsRefresh).

---

Duplicate comments:
In `@src/commands/install-skill.ts`:
- Around line 305-336: The loop currently sets agentSucceeded true after the
first successful install so partially-updated agents get marked current and the
full requested skills list is returned; change the logic in the block that
iterates detected agents to track per-agent per-skill success (call installSkill
for each skill, record each result), set agentSucceeded to true only if every
installSkill returned result.success (or equivalently if there are no failures),
write the SKILL_VERSION_MARKER_FILENAME via writeFile only when agentSucceeded
is true, and adjust the returned payload so it does not advertise the full
requested skills set for an agent that had partial failures (use the actual
successful installs rather than the original skills array); reference
functions/variables installSkill, agentSucceeded, succeededAgents,
perAgentBefore, perAgentAfter, writeFile, and SKILL_VERSION_MARKER_FILENAME when
updating the code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5e013ffb-f61a-499f-9896-2aa129249ab7

📥 Commits

Reviewing files that changed from the base of the PR and between 3476e85 and e49149e.

📒 Files selected for processing (10)
  • src/bin.ts
  • src/commands/doctor.ts
  • src/commands/install-skill.spec.ts
  • src/commands/install-skill.ts
  • src/commands/login.spec.ts
  • src/commands/login.ts
  • src/doctor/checks/skills-fix.spec.ts
  • src/doctor/index.ts
  • src/doctor/output.ts
  • src/doctor/types.ts

Comment thread src/commands/install-skill.spec.ts
Comment thread src/commands/install-skill.ts Outdated
Comment on lines +290 to +337
export async function refreshWorkOSSkills(opts: RefreshOptions = {}): Promise<RefreshResult | null> {
const home = homedir();
const skillsDir = getSkillsDir();
const detected = opts.agents ?? detectAgents(createAgents(home));
const allSkills = await discoverSkills(skillsDir).catch(() => []);
const skills = opts.skills ? allSkills.filter((s) => opts.skills!.includes(s)) : allSkills;
const writeMarker = opts.writeMarker ?? true;

if (skills.length === 0 || detected.length === 0) return null;

if (skills.length === 0 || targetAgents.length === 0) return;
const version = getBundledSkillsVersion(skillsDir);
const perAgentBefore: Record<string, string | null> = {};
const perAgentAfter: Record<string, string | null> = {};
const succeededAgents: AgentConfig[] = [];

for (const agent of detected) {
perAgentBefore[agent.name] = readSkillVersionMarker(agent);

let agentSucceeded = false;
for (const skill of skills) {
for (const agent of targetAgents) {
await installSkill(skillsDir, skill, agent);
const result = await installSkill(skillsDir, skill, agent);
if (result.success) agentSucceeded = true;
}

if (agentSucceeded) {
succeededAgents.push(agent);
if (writeMarker && version) {
try {
await writeFile(join(agent.globalSkillsDir, SKILL_VERSION_MARKER_FILENAME), version, 'utf8');
} catch {
// Marker is best-effort; doctor treats missing marker as "unknown".
}
}
}

perAgentAfter[agent.name] = readSkillVersionMarker(agent);
}

if (succeededAgents.length === 0) return null;

return {
agents: succeededAgents,
skills,
version,
perAgentBefore,
perAgentAfter,
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Route the explicit install command through the new refresh path.

refreshWorkOSSkills() is now the only place that writes .workos-skill-version, but runInstallSkill() still installs skills via its old loop. That means a user can follow the SKILLS_OUTDATED remediation with workos skills install and still keep an old or missing marker on the next doctor run.

Also applies to: 339-349

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

In `@src/commands/install-skill.ts` around lines 290 - 337, The explicit install
flow should reuse refreshWorkOSSkills so markers get written; change
runInstallSkill (and the other direct-install path that currently loops 339-349)
to call refreshWorkOSSkills with the requested skills and detected agents (e.g.
refreshWorkOSSkills({ skills: requestedSkills, agents: detectedAgents,
writeMarker: true })) instead of performing the old per-skill install loop using
installSkill directly, ensuring the returned RefreshResult is propagated so
.workos-skill-version markers are created consistently.

Comment thread src/commands/login.ts
Phase 1 of cus-feedback-fixes is now live on npm. Pulling the new
content in so the recursive prune-replace install machinery added in
Phase 2 actually delivers the new guardrails:

* references/workos-cli-upgrade.md (CLI upgrade-path topic)
* SKILL.md routing rule for outdated-CLI symptoms
* references/workos-management.md "Detecting CLI upgrades" subsection

Exact pin matching the prior 0.2.4 convention.
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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 54: The package.json dependency pin for "@workos/skills": "0.4.0" is
invalid and will fail install; update that dependency entry to a released
version (for example "0.2.4") by replacing the current version string for the
"@workos/skills" key with the actual released version (e.g., change
"@workos/skills": "0.4.0" to "@workos/skills": "0.2.4") and run install to
verify.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 65e7409a-38f3-4391-a6df-1da880c09e79

📥 Commits

Reviewing files that changed from the base of the PR and between e49149e and f0e4f9b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • package.json

Comment thread package.json
"@napi-rs/keyring": "^1.2.0",
"@workos-inc/node": "^8.7.0",
"@workos/skills": "0.2.4",
"@workos/skills": "0.4.0",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

For npm package @workos/skills` version 0.4.0, confirm:

  1. it still exports getSkillsDir,
  2. its package layout still supports resolving package.json by walking up three directories from getSkillsDir() result,
  3. there were no breaking changes from 0.2.4 that affect CLI embedding/install workflows.
    Please include links to changelog/release notes or package source.`

💡 Result:

No evidence found for npm package @workos/skills version 0.4.0 existing as of 2026-04-26. Latest documented version is 0.2.4 (https://github.com/workos/skills/releases/tag/v0.2.4), with later releases up to 0.3.0 mentioned in PRs but no 0.4.0. Repo README and docs explicitly list getSkillsDir as exported in current versions (https://github.com/workos/skills), described as returning absolute path to the skills/ directory containing skill folders like workos/. 1) Cannot confirm for non-existent v0.4.0; exported in latest available (0.2.4+). 2) Cannot confirm layout for v0.4.0. Repo structure shows skills/ under repo root (https://github.com/workos/skills/blob/main/CLAUDE.md); when installed via npm, node_modules/@workos/skills/package.json is at module root. Typical path resolution from skills/ dir (getSkillsDir) would be .. (to @workos/skills) -> ../../ (to project root) -> ../../.. (outside), but "three directories up" likely reaches project root package.json if skills/ is nested as plugins/workos/skills/ per CLAUDE.md. No breaking layout changes evident. 3) From 0.2.4 changelog: only bug fix for Next.js AuthKit skill (no CLI/embedding impact). Earlier releases (0.2.1-0.2.3) are minor bug fixes/package tweaks (https://github.com/workos/skills/releases). No breaking changes affecting CLI embedding/install workflows identified. Sources: GitHub repo (1,4,6), releases (2,47-49), libraries.io (3), PRs (50,51). Cannot verify v0.4.0 specifics due to lack of evidence.

Citations:


@workos/skills@0.4.0 does not exist; update to latest available version (0.2.4).

The web search confirmed that @workos/skills version 0.4.0 has not been released. The latest documented version is 0.2.4 (https://github.com/workos/skills/releases/tag/v0.2.4). Versions 0.3.0 have been mentioned in PRs but lack official releases. This dependency will fail at install time. Update the pin to an actual released version such as 0.2.4 or 0.3.0 (if officially available in your npm registry).

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

In `@package.json` at line 54, The package.json dependency pin for
"@workos/skills": "0.4.0" is invalid and will fail install; update that
dependency entry to a released version (for example "0.2.4") by replacing the
current version string for the "@workos/skills" key with the actual released
version (e.g., change "@workos/skills": "0.4.0" to "@workos/skills": "0.2.4")
and run install to verify.

@nicknisi nicknisi changed the title feat: surface skill auto-install and detect outdated bundled content feat: complete WorkOS skill install + refresh loop with doctor --fix Apr 26, 2026
Review surface findings on PR #130. Each fix below addresses a
specific reviewer comment.

* `refreshWorkOSSkills` no longer over-reports installed skill count.
  Tracks the union of skills that succeeded for at least one agent and
  returns that as `RefreshResult.skills` instead of the full filtered
  attempt list. Fixes the "Installed N skills" line inflating when
  some skills failed to copy. (Greptile P1, CodeRabbit Major)

* `runInstallSkill` (the explicit `workos skills install` command) now
  writes per-agent `.workos-skill-version` markers after success, so
  `workos doctor` doesn't immediately re-flag the freshly-installed
  skills as stale. Closes the loop where the SKILLS_OUTDATED
  remediation pointed at an install path that didn't update markers.
  (Codex P2, CodeRabbit Major)

* `installSkill` setup operations (mkdir, mkdtemp, cleanupStaleOrphans)
  now run inside the try block. Filesystem errors before the copy
  (EACCES, ENOTDIR, etc.) surface as `{ success: false }` instead of
  rejecting — both runInstallSkill and refreshWorkOSSkills accumulate
  per-(skill, agent) failures, and a single bad agent dir would
  otherwise abort the whole batch (and now: abort the entire `doctor
  --fix` run). (Codex P2)

* `checkSkills` only reports agents that actually have a WorkOS skill
  installed (marker file OR `workos/` subdir present), instead of any
  agent with a `skills/` directory. Without this, a Claude Code user
  who had unrelated skills but never installed WorkOS would have
  `installedVersion === null` reported, which `--fix` would then
  interpret as "missing marker, refresh" and write `workos/` +
  `workos-widgets/` to that agent unprompted. (Codex P2)

* Staleness check uses semver ordering instead of string inequality.
  String comparison would flag `installed > bundled` (downgrade
  scenario: user installed via newer CLI then downgraded) as stale
  and the SKILLS_OUTDATED remediation would silently downgrade their
  agent's skills. Falls back to string inequality when either version
  is non-semver. (Greptile P2)

* `installSkillsAfterLogin` wraps `autoInstallSkills` in a 30s
  Promise.race timeout. The hook can no longer block login completion
  on a hung filesystem call. (CodeRabbit Major; was an open item
  in the spec)

* `maybeRefreshSkills` runs BEFORE `earlyIssues` and AI analysis in
  runDoctor, so AI prompt context (and downstream issue detection)
  see the post-refresh state. Without this move, a successful `--fix`
  could clear `report.issues` while `report.aiAnalysis` still
  references the just-fixed SKILLS_OUTDATED warning. (CodeRabbit Major,
  outside-diff finding)

* `--fix` flag now appears in `workos doctor --help --json` via
  `src/utils/help-json.ts`. The CLI intercepts `--help --json` from
  this static registry; without the entry, automation/agent consumers
  (the same audience this whole feature targets) wouldn't discover
  `--fix` from machine-readable help. (Codex P3)

* `install.spec.ts` mocks for `autoInstallSkills` now include the
  required `version` field, matching the AutoInstallResult type.
  (Greptile P2)

* `install-skill.spec.ts` "writes a version marker per agent when the
  bundled version is resolvable" now plants a deterministic
  `<packageRoot>/package.json` + `<packageRoot>/plugins/workos/skills/`
  layout so `getBundledSkillsVersion` returns a known value (`9.9.9`)
  and the marker-write success path is genuinely exercised. (CodeRabbit
  Minor)

Pushed back / dismissed:

* CodeRabbit Critical "0.4.0 doesn't exist": false positive. The
  registry data the bot's web search saw was stale; `npm view
  @workos/skills@0.4.0 version` returns `0.4.0` and `pnpm install`
  resolves cleanly.

* CodeRabbit Major "use async fs in getBundledSkillsVersion": the
  file's pre-existing convention is sync APIs for one-shot path
  probes (`existsSync`, `readFileSync` in checkSkills, install-skill).
  Converting one function would cascade through `checkSkills` →
  `runDoctor` for marginal benefit. Keeping consistent.
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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/commands/install-skill.ts (1)

181-258: ⚠️ Potential issue | 🟠 Major

Add JSON-mode handling to skills install.

This handler still emits only human text and exits directly, so automation cannot get structured output from workos skills install --json. Please branch on the global output mode here and add matching JSON-mode coverage in src/commands/install-skill.spec.ts.

As per coding guidelines, "Implement both human and JSON output modes in commands; check OutputMode usage in src/bin.ts" and "Write .spec.ts test files alongside every command file and include JSON mode tests".

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

In `@src/commands/install-skill.ts` around lines 181 - 258, runInstallSkill
currently only emits human-readable logs and calls process.exit, so automation
can't consume structured output; update runInstallSkill to branch on the global
OutputMode (see OutputMode usage in src/bin.ts) and emit a JSON object
(including arrays of successes and failures with skill, agent.displayName, and
error fields, plus overall status and bundled skills version) when mode ===
'json' instead of human console output, ensuring exit code reflects failure when
any installs fail; retain existing human output branch. Also add JSON-mode unit
tests in src/commands/install-skill.spec.ts that invoke runInstallSkill (or the
CLI entry) with OutputMode set to json and assert the emitted JSON structure for
both all-success and partial-failure cases, and include a test that verifies
version marker writing behavior tied to getBundledSkillsVersion, writeFile, and
SKILL_VERSION_MARKER_FILENAME.
♻️ Duplicate comments (1)
src/commands/install-skill.ts (1)

233-246: ⚠️ Potential issue | 🟠 Major

Only write the version marker after a full per-agent success.

Both branches persist .workos-skill-version as soon as any requested skill lands for an agent. If one skill fails, checkSkills() will still treat that agent as current and doctor --fix stops retrying the missing install.

Possible fix
-  const version = getBundledSkillsVersion(skillsDir);
-  if (version) {
-    const succeededAgents = new Set<AgentConfig>();
-    for (const r of successful) succeededAgents.add(r.agent);
-    for (const agent of succeededAgents) {
+  const version = getBundledSkillsVersion(skillsDir);
+  if (version) {
+    const agentResults = new Map<AgentConfig, { successes: number; failures: number }>();
+    for (const r of results) {
+      const current = agentResults.get(r.agent) ?? { successes: 0, failures: 0 };
+      if (r.success) current.successes += 1;
+      else current.failures += 1;
+      agentResults.set(r.agent, current);
+    }
+    for (const [agent, summary] of agentResults) {
+      if (summary.failures > 0 || summary.successes === 0) continue;
       try {
         await writeFile(join(agent.globalSkillsDir, SKILL_VERSION_MARKER_FILENAME), version, 'utf8');
       } catch {
         // Marker is best-effort; doctor treats missing marker as "unknown".
       }
     }
   }

Also applies to: 339-347

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

In `@src/commands/install-skill.ts` around lines 233 - 246, Only write the
.workos-skill-version marker when every requested skill for that specific agent
succeeded: build a per-agent set (or count) of requested installs and compare it
to the per-agent successes in successful (instead of marking an agent when any
single install succeeded via succeededAgents); only call
writeFile(join(agent.globalSkillsDir, SKILL_VERSION_MARKER_FILENAME), version,
'utf8') for agents where successful entries fully cover that agent's requested
skills so checkSkills() won't treat partially-installed agents as current.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/login.ts`:
- Around line 90-91: The timeout Promise created with setTimeout (using
SKILL_INSTALL_TIMEOUT_MS) leaves an active timer after
Promise.race([autoInstallSkills(), timeout]) resolves, preventing Node from
exiting; fix by storing the timer handle returned by setTimeout and either call
clearTimeout(timer) in a finally block after awaiting the race or call
timer.unref() immediately so the pending timer does not keep the event loop
alive; update the code around the timeout/Promise.race invocation (the timeout
variable, SKILL_INSTALL_TIMEOUT_MS, and autoInstallSkills()) accordingly.

In `@src/doctor/checks/skills.ts`:
- Around line 47-48: The check currently only treats the presence of workos/ or
the markerPath as a signal to skip processing, which misses agents that have the
older explicit install directory 'workos-widgets'; update the condition in the
loop that computes workosSkillDir (variable workosSkillDir, markerPath and
agent.globalSkillsDir) to also check for the existence of a workos-widgets
directory (e.g., join(agent.globalSkillsDir, 'workos-widgets')) and treat it the
same as workos when deciding to continue; in short, include
existsSync(join(agent.globalSkillsDir, 'workos-widgets')) as an alternative in
the OR check so agents with workos-widgets are recognized as pre-marker
installs.

---

Outside diff comments:
In `@src/commands/install-skill.ts`:
- Around line 181-258: runInstallSkill currently only emits human-readable logs
and calls process.exit, so automation can't consume structured output; update
runInstallSkill to branch on the global OutputMode (see OutputMode usage in
src/bin.ts) and emit a JSON object (including arrays of successes and failures
with skill, agent.displayName, and error fields, plus overall status and bundled
skills version) when mode === 'json' instead of human console output, ensuring
exit code reflects failure when any installs fail; retain existing human output
branch. Also add JSON-mode unit tests in src/commands/install-skill.spec.ts that
invoke runInstallSkill (or the CLI entry) with OutputMode set to json and assert
the emitted JSON structure for both all-success and partial-failure cases, and
include a test that verifies version marker writing behavior tied to
getBundledSkillsVersion, writeFile, and SKILL_VERSION_MARKER_FILENAME.

---

Duplicate comments:
In `@src/commands/install-skill.ts`:
- Around line 233-246: Only write the .workos-skill-version marker when every
requested skill for that specific agent succeeded: build a per-agent set (or
count) of requested installs and compare it to the per-agent successes in
successful (instead of marking an agent when any single install succeeded via
succeededAgents); only call writeFile(join(agent.globalSkillsDir,
SKILL_VERSION_MARKER_FILENAME), version, 'utf8') for agents where successful
entries fully cover that agent's requested skills so checkSkills() won't treat
partially-installed agents as current.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f2a42966-9ecf-44fa-902d-7d97fcc8c11b

📥 Commits

Reviewing files that changed from the base of the PR and between 147a8e8 and 0a7b963.

📒 Files selected for processing (8)
  • src/commands/install-skill.spec.ts
  • src/commands/install-skill.ts
  • src/commands/install.spec.ts
  • src/commands/login.ts
  • src/doctor/checks/skills.spec.ts
  • src/doctor/checks/skills.ts
  • src/doctor/index.ts
  • src/utils/help-json.ts

Comment thread src/commands/login.ts Outdated
Comment thread src/doctor/checks/skills.ts Outdated
Comment thread src/doctor/checks/skills.ts Outdated
Three findings I missed in the previous review-fix pass plus the
broader sync-fs cascade I had pushed back on. Reconsidered after
re-reading CLAUDE.md's 'Avoid Node-specific sync APIs' guideline as
a project rule rather than a suggestion.

* `installSkillsAfterLogin`'s setTimeout handle is now stored, both
  `unref()`'d on creation and `clearTimeout()`'d in a finally block.
  Without this, when `autoInstallSkills` won the race the pending
  timer kept the Node event loop alive for up to 30s and the CLI
  appeared to hang. (CodeRabbit, login.ts:91)

* `checkSkills` now treats `<agent>/skills/workos-widgets/` as a
  pre-marker install signal in addition to `workos/`. An agent that
  only had `workos-widgets/` from an older explicit install was
  invisible to doctor under the previous narrowing. (CodeRabbit,
  skills.ts:48)

* Sync fs APIs replaced with async equivalents in install-skill.ts
  and doctor/checks/skills.ts:
  - `getBundledSkillsVersion` → async (await readFile)
  - `readSkillVersionMarker` → async (await readFile + access-based pathExists)
  - `checkSkills` → async (cascades to runDoctor, maybeRefreshSkills)
  - `installSkill`'s `existsSync(targetDir)` → `pathExists`
  - `cleanupStaleOrphans`'s `existsSync(parent)` → `pathExists`
  - `discoverSkills` filter uses `Promise.all(pathExists(...))`
  - `existsSync` import retained only for `agent.detect()` callbacks
    (sync-by-design boolean predicates inside detection table)
  (CodeRabbit, install-skill.ts:28+132 and skills.ts:5)

Plus an opportunistic cleanup that came out of the cascade:

* Extracted `writeAgentSkillMarker` helper so `runInstallSkill` and
  `refreshWorkOSSkills` share the same best-effort marker semantics
  (single source of truth, no behavioral drift). Partially addresses
  CodeRabbit's "route runInstallSkill through refreshWorkOSSkills" —
  the marker-write logic is the actual functional duplication and is
  now shared; the install loop itself stays separate because
  runInstallSkill needs granular per-(skill, agent) failure rendering
  that the primitive doesn't expose.

Tests:
- `skills.spec.ts` updated for async checkSkills + new test for
  workos-widgets-only install + new test for downgrade scenario
  (installed > bundled must NOT be flagged stale, semver fix)
- `skills-fix.spec.ts` switched to mockResolvedValueOnce for the now
  async checkSkills mock
- All 1614 tests pass
Comment thread src/doctor/output.ts
Comment on lines +163 to +168
const slugs = Object.keys(report.skillsRefresh.before);
for (const slug of slugs) {
const before = formatVersion(report.skillsRefresh.before[slug] ?? null);
const after = formatVersion(report.skillsRefresh.after[slug] ?? null);
console.log(` ${Chalk.green('✓')} Updated WorkOS skills for ${displayName(slug)}: ${before} → ${after}`);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 ✓ Updated rendered for agents where install failed

report.skillsRefresh.before (and .after) are keyed by agent.name for every detected agent, including those where all skill installs failed. refreshWorkOSSkills populates perAgentBefore/perAgentAfter inside the full detected loop, but only adds agents to succeededAgents when at least one install succeeds.

For a partially-failed run (e.g., Claude Code succeeds, Codex fails), the current code prints:

✓ Updated WorkOS skills for Codex: (none) → (none)

— a green checkmark with no version change, which users will reasonably read as a success.

Scope the rendered slugs to agents where before !== after:

const slugs = Object.keys(report.skillsRefresh.before).filter(
  (slug) => report.skillsRefresh!.before[slug] !== report.skillsRefresh!.after[slug],
);

@nicknisi nicknisi merged commit c61f5a4 into main Apr 26, 2026
6 checks passed
@nicknisi nicknisi deleted the nicknisi/cus-feedback branch April 26, 2026 22:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant