Skip to content

Fix stale OpenCode plugin cache doctor check#199

Merged
alfonso-magic-context merged 3 commits into
cortexkit:masterfrom
coleleavitt:fix/opencode-stale-plugin-cache-doctor
Jun 28, 2026
Merged

Fix stale OpenCode plugin cache doctor check#199
alfonso-magic-context merged 3 commits into
cortexkit:masterfrom
coleleavitt:fix/opencode-stale-plugin-cache-doctor

Conversation

@coleleavitt

@coleleavitt coleleavitt commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • compare the cached OpenCode plugin package against the actual npm latest plugin version, not just the CLI package version
  • extract OpenCode plugin cache handling into a focused helper module
  • add regression coverage for stale @latest cache (0.26.0 cached vs 0.29.1 latest) and matching-cache no-op behavior

Verification

  • bun run test
  • bun run typecheck
  • bun run lint
  • bun run build
  • manual QA: ran bun src/index.ts doctor --harness opencode; it cleared cached @cortexkit/opencode-magic-context 0.26.0 and opencode restarted with Magic Context 0.29.1

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Fixes stale OpenCode plugin cache handling in doctor by comparing the cached @cortexkit/opencode-magic-context to the plugin’s npm latest and safely handling offline cases. Adds shared helpers for plugin cache paths and version files to reduce duplication and improve detection across commands.

  • Bug Fixes

    • Compares the cached plugin against npm latest and clears only when outdated; also clears versionless cache folders.
    • If the plugin npm latest can’t be fetched, preserves the cache and reports a check-unavailable status; --force clears it.
    • Embedding runtime check now uses shared cache roots to avoid false negatives.
  • Refactors

    • Added lib/opencode-plugin-cache with OPENCODE_PLUGIN_NAME, OPENCODE_PLUGIN_ENTRY_WITH_VERSION, getOpenCodePluginCacheRoots, and getOpenCodePluginPackageJsonPaths; adopted in doctor-opencode, diagnostics, setup, and adapter.
    • Introduced doctor-opencode-cache.clearPluginCache({ force, latestVersion }); tests cover stale, matching, versionless, offline, and force-clear paths.

Written for commit 0b02bc2. Summary will update on new commits.

Review in cubic

Greptile Summary

This PR fixes the stale OpenCode plugin cache check in doctor by fetching the plugin package's own npm latest version (instead of using the CLI package version as a proxy), then clearing the cache only when it is outdated. It also extracts shared cache path helpers and plugin name constants into a new lib/opencode-plugin-cache.ts module, and adds a focused commands/doctor-opencode-cache.ts helper with full regression coverage.

  • Core fix: doctor-opencode.ts now fetches pluginNpmLatest for @cortexkit/opencode-magic-context in parallel with the CLI npm check and passes it to clearPluginCache; the new check_unavailable result action handles the offline case gracefully (preserves cache, warns the user, respects --force).
  • Deduplication: OPENCODE_PLUGIN_NAME, OPENCODE_PLUGIN_ENTRY_WITH_VERSION, getOpenCodePluginCacheRoots, and getOpenCodePluginPackageJsonPaths are now defined once and imported everywhere, eliminating four separate local constant definitions.
  • Test coverage: Five new tests cover stale-cache clearing, current-cache no-op, versionless-cache clearing, offline preservation, and force-clear; XDG_CACHE_HOME is correctly saved and restored per test.

Confidence Score: 5/5

Safe to merge; the bug fix is well-scoped and all affected code paths are covered by new tests.

The change correctly queries the plugin's own npm registry endpoint rather than using the CLI version as a stand-in, and the offline/force/versionless edge cases are all exercised by the new test suite. The only outstanding item is a diagnostic report field that still echoes the CLI version as "latest" for the plugin — an informational inaccuracy that does not affect doctor's decision-making logic.

packages/cli/src/lib/diagnostics-opencode.ts — getPluginCacheInfo still returns getSelfVersion() as the plugin's latest, which can misrepresent the plugin version in bug-report bundles.

Important Files Changed

Filename Overview
packages/cli/src/lib/opencode-plugin-cache.ts New shared module exporting plugin name constants and cache path helpers; eliminates duplicate constant definitions across four files.
packages/cli/src/commands/doctor-opencode-cache.ts New helper that encapsulates plugin-cache clearing logic; correctly handles stale, current, versionless, offline, and force-clear paths with a clean result union type.
packages/cli/src/commands/doctor-opencode.ts Core fix: now fetches plugin npm latest in parallel with CLI npm latest and passes it to clearPluginCache; adds handling for the new check_unavailable result action.
packages/cli/src/commands/doctor-opencode.test.ts Adds five focused regression tests covering stale, matching, versionless, offline-preserve, and force-clear scenarios; XDG_CACHE_HOME is correctly saved and restored in afterEach.
packages/cli/src/lib/diagnostics-opencode.ts Adopts shared cache helpers; getPluginCacheInfo still uses getSelfVersion() for the latest field rather than the plugin's actual npm latest, a minor diagnostic inaccuracy.
packages/cli/src/adapters/opencode.ts Replaces inline candidate-list construction with getOpenCodePluginPackageJsonPaths(); removes local duplicate constants.
packages/cli/src/commands/setup-opencode.ts Replaces local PLUGIN_NAME/PLUGIN_ENTRY constants with shared imports; no behavioral change.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant D as doctor-opencode.ts
    participant N as npm registry
    participant C as doctor-opencode-cache.ts
    participant FS as filesystem

    D->>N: "fetchNpmLatest("@cortexkit/magic-context")"
    D->>N: "fetchNpmLatest("@cortexkit/opencode-magic-context")"
    N-->>D: npmLatest (CLI)
    N-->>D: pluginNpmLatest (plugin, or null if offline)

    D->>C: "clearPluginCache({ force, latestVersion: pluginNpmLatest })"
    C->>FS: getOpenCodePluginCacheRoots()
    FS-->>C: existingRoots

    alt no cache found
        C-->>D: "{ action: not_found }"
    else "latestVersion=null and not forced"
        C-->>D: "{ action: check_unavailable }"
    else latestVersion provided
        C->>FS: readCachedPluginVersion(each root)
        FS-->>C: cached versions
        C->>C: "filter cached !== latestVersion"
        alt all entries up to date
            C-->>D: "{ action: up_to_date }"
        else stale or versionless entries
            C->>FS: rmSync(clearTargets)
            C-->>D: "{ action: cleared, paths }"
        end
    else "force=true"
        C->>FS: rmSync(all roots)
        C-->>D: "{ action: cleared }"
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant D as doctor-opencode.ts
    participant N as npm registry
    participant C as doctor-opencode-cache.ts
    participant FS as filesystem

    D->>N: "fetchNpmLatest("@cortexkit/magic-context")"
    D->>N: "fetchNpmLatest("@cortexkit/opencode-magic-context")"
    N-->>D: npmLatest (CLI)
    N-->>D: pluginNpmLatest (plugin, or null if offline)

    D->>C: "clearPluginCache({ force, latestVersion: pluginNpmLatest })"
    C->>FS: getOpenCodePluginCacheRoots()
    FS-->>C: existingRoots

    alt no cache found
        C-->>D: "{ action: not_found }"
    else "latestVersion=null and not forced"
        C-->>D: "{ action: check_unavailable }"
    else latestVersion provided
        C->>FS: readCachedPluginVersion(each root)
        FS-->>C: cached versions
        C->>C: "filter cached !== latestVersion"
        alt all entries up to date
            C-->>D: "{ action: up_to_date }"
        else stale or versionless entries
            C->>FS: rmSync(clearTargets)
            C-->>D: "{ action: cleared, paths }"
        end
    else "force=true"
        C->>FS: rmSync(all roots)
        C-->>D: "{ action: cleared }"
    end
Loading

Reviews (4): Last reviewed commit: "Fix offline plugin cache handling" | Re-trigger Greptile

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2 issues found across 3 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/cli/src/commands/doctor-opencode-cache.ts Outdated
Comment thread packages/cli/src/commands/doctor-opencode.ts Outdated
Comment thread packages/cli/src/commands/doctor-opencode.ts Outdated
Comment thread packages/cli/src/commands/doctor-opencode-cache.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2 issues found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/cli/src/commands/doctor-opencode-cache.ts">

<violation number="1" location="packages/cli/src/commands/doctor-opencode-cache.ts:5">
P1: Exported plugin constants are duplicated in other modules; adapters/opencode.ts hard-codes the same cache paths instead of using the new helper, risking divergence and stale-cache regressions.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@@ -0,0 +1,100 @@
import { existsSync, readFileSync, rmSync } from "node:fs";

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: Exported plugin constants are duplicated in other modules; adapters/opencode.ts hard-codes the same cache paths instead of using the new helper, risking divergence and stale-cache regressions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/commands/doctor-opencode-cache.ts, line 5:

<comment>Exported plugin constants are duplicated in other modules; adapters/opencode.ts hard-codes the same cache paths instead of using the new helper, risking divergence and stale-cache regressions.</comment>

<file context>
@@ -2,12 +2,13 @@ import { existsSync, readFileSync, rmSync } from "node:fs";
 
-const PLUGIN_NAME = "@cortexkit/opencode-magic-context";
-const PLUGIN_ENTRY_WITH_VERSION = `${PLUGIN_NAME}@latest`;
+export const OPENCODE_PLUGIN_NAME = "@cortexkit/opencode-magic-context";
+export const OPENCODE_PLUGIN_ENTRY_WITH_VERSION = `${OPENCODE_PLUGIN_NAME}@latest`;
 
</file context>

Comment thread packages/cli/src/commands/doctor-opencode.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 7 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/cli/src/commands/doctor-opencode-cache.ts">

<violation number="1" location="packages/cli/src/commands/doctor-opencode-cache.ts:17">
P2: Bulk cache deletion is not atomic and fails to report partial-clear outcomes, so a mid-loop deletion failure leaves the cache partially cleared while the returned result reports a single `error` path that may already be deleted.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

}

function readCachedPluginVersion(pluginCacheDir: string): string | undefined {
try {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Bulk cache deletion is not atomic and fails to report partial-clear outcomes, so a mid-loop deletion failure leaves the cache partially cleared while the returned result reports a single error path that may already be deleted.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/commands/doctor-opencode-cache.ts, line 17:

<comment>Bulk cache deletion is not atomic and fails to report partial-clear outcomes, so a mid-loop deletion failure leaves the cache partially cleared while the returned result reports a single `error` path that may already be deleted.</comment>

<file context>
@@ -0,0 +1,89 @@
+}
+
+function readCachedPluginVersion(pluginCacheDir: string): string | undefined {
+    try {
+        const installedPkgPath = getOpenCodePluginPackageJsonPath(pluginCacheDir);
+        if (!existsSync(installedPkgPath)) return undefined;
</file context>

@alfonso-magic-context alfonso-magic-context merged commit d4b15b7 into cortexkit:master Jun 28, 2026
12 checks passed
ualtinok added a commit that referenced this pull request Jun 28, 2026
Follow-up to #199 (cubic P2): the bulk cache clear ran all roots inside one
try/catch, so a mid-loop rmSync failure aborted the rest and the error result
reported clearTargets[0] - a root that may have already been deleted - while
hiding that one root was cleared. Now each root is removed independently: the
error result points at the root that ACTUALLY failed, successfully-cleared roots
still get removed, and partial outcomes are reported accurately. Added an
injected-remover seam so the per-root failure path is deterministically tested.

Gate: CLI 213/0, tsc + biome clean.

Co-authored-by: Alfonso [Magic Context] <288211368+alfonso-magic-context@users.noreply.github.com>
@coleleavitt coleleavitt deleted the fix/opencode-stale-plugin-cache-doctor branch June 28, 2026 06:58
ualtinok added a commit that referenced this pull request Jun 28, 2026
Bump the in-app announcement to 0.30.0 (Desktop detection #196, badge #198,
doctor cache #199) and add release notes.

Fix a SQLITE_BUSY flake in thinking-block-safety.test.ts: openContextDbWritable
opened the context DB without a busy_timeout, so under loaded CI a write could
collide with the live plugin's handle and fail immediately instead of waiting.
Added PRAGMA busy_timeout = 5000, matching the other e2e tests that share the
context DB. Not a logic change; the audit fixes do not touch the tagging path
this test exercises.

Co-authored-by: Alfonso [Magic Context] <288211368+alfonso-magic-context@users.noreply.github.com>
@alfonso-magic-context

Copy link
Copy Markdown
Collaborator

Shipped in v0.30.0. Thanks for the fix, @coleleavitt, the root cause was spot on: the check compared against the CLI's own version rather than the plugin's latest published version, so a stale @latest cache could survive when run through an older cached CLI.

Merged with a small follow-up so each cache root is cleared independently (a failure on one root reports the root that actually failed, not an already-deleted one) and the cache is preserved rather than cleared when the version check is offline.

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