Skip to content

fix(core): preserve npm lock when plugin SDK resolves locally #541

Description

@LeXwDeX

This was generated by AI during triage.

Why

OpenCode 1.0.39 rewrites or deletes the global config package-lock.json whenever TUI startup resolves @opencode-ai/plugin from an existing local or bundled copy. The subsequent dirty check sees no lockfile, concludes declared dependencies are missing, and runs Arborist reify again. Users therefore observe npm install on every OpenCode launch.

Reproduction

Using an isolated XDG_CONFIG_HOME containing opencode/package.json, a valid npm lock and installed @opencode-ai/plugin:

  1. Start opencode . under a PTY and exit after initialization.
  2. First run rewrites the lockfile with byte-identical content (mtime changes).
  3. Second run reaches a state where package-lock.json is deleted while the reinstall is running.

The headless opencode serve path does not reproduce because it does not wait for the TUI dependency bootstrap.

Root cause

packages/core/src/npm.ts:220-222 removes package-lock.json whenever at least one requested dependency was satisfied locally/bundled (remaining.length !== requested.length). The same call then performs the normal dirty check at lines 245-273. Because the lock was just deleted, every dependency declared by package.json appears unlocked and reify() runs.

This behavior was introduced by b5a1c36494 (fix(core): bundle plugin SDK and degrade references for offline startup). Its two tests currently assert that the lockfile is absent, but do not invoke Npm.install twice or assert that reification was skipped.

Scope

  • Preserve a valid npm lock when the requested plugin SDK is already available locally or from the bundled SDK.
  • Skip registry/reify work when local/bundled resolution plus the existing dependency state is clean.
  • Keep registry fallback working when the plugin SDK is genuinely unavailable or another dependency is missing.
  • Replace the lock-deletion pins with deterministic repeated-install regression tests for both local and bundled plugin paths.
  • Add an isolated TUI startup regression that runs twice and asserts the config lockfile remains byte- and mtime-stable after dependency initialization.

Acceptance

  • Two consecutive Npm.install calls with an existing local plugin and valid lock do not delete/rewrite the lock and do not invoke Arborist reify.
  • The same guarantee holds when the bundled SDK is copied into place.
  • Missing node_modules and genuine lock/package drift still invoke reify and produce a valid lock.
  • Two isolated 1.0.39-equivalent TUI initializations leave package-lock.json present with unchanged bytes and mtime after the first stable run.
  • Focused core npm/config/TUI tests, affected package typechecks, and lint pass with no new warnings.

Non-goals

  • Changing package managers, lockfile formats, plugin SDK version policy, or the local > bundled > registry precedence.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingkind::fixready-for-agentFully specified and ready for an agent

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions