DeepDojo.AI发布 2026-11-13更新 2026-11-13English

能回滚的 Claude Code 插件升级流程

Claude CodeAI 编程

团队插件用了一段时间,总会迎来升级:新版本修了 bug、加了功能,大家照常更新。这次的新版本在启动时,顺手把插件的本地缓存迁移成了新的存储格式。

没过多久,它在几位同事那里大面积报错。你赶紧把插件退回旧版,以为工作流就此复原,结果旧版一读到新格式的缓存文件,照样因为解析失败而崩溃。

问题出在“代码版本”和“持久数据”被当成了一件事。插件代码可以随版本回退,运行中产生的缓存、索引和状态却留在原地,已经变成了新格式。插件分发出去之后,还有几类东西要单独管:

  • 敏感配置:令牌标记为敏感后存进了安全存储,后续脚本却可能把它打印进日志;
  • 运行数据:写进安装目录的缓存,每次升级都可能被整个替换掉;
  • 升级回滚:代码能退回旧版,数据未必能。

办法是把这几条线分开设计:敏感值从保存到使用全程追一遍;运行数据写进专门的持久目录;升级前记下旧版本、备份数据,走一遍常用调用,确认能回滚再向团队推广。

01 敏感配置:存好之后,还要看怎么用

插件可以在清单中声明用户配置。敏感项标记为 sensitive: true,按 Claude Code 的插件参考,输入时会被遮盖,值存进安全存储,而不是写进 settings.json。

{
  "name": "team-notify",
  "version": "1.0.0",
  "userConfig": {
    "channel": {
      "type": "string",
      "title": "通知频道",
      "description": "默认通知频道"
    },
    "token": {
      "type": "string",
      "title": "通知服务凭据",
      "description": "通知服务 token",
      "sensitive": true
    }
  }
}

配置值分开管理:

内容处理方式
频道、API endpoint、检查开关、团队名、默认环境普通 userConfig
私有 token 等秘密敏感 userConfig、环境变量或系统凭据存储;不写进模板、脚本和 .mcp.json

标记完敏感项,还要沿着值往后查:它被哪个脚本读取,脚本会不会把它打印出来?官方说明,Hook 进程会以环境变量的形式拿到每一项用户配置。专门的存储解决的是保存问题,挡不住后续使用时写进日志。逐项找出泄露会造成影响的内容,再核对当前版本的保存方式和实际调用。

02 运行数据写进 CLAUDE_PLUGIN_DATA

插件可以用 ${CLAUDE_PLUGIN_DATA} 保存自己的运行数据,适合放缓存、本地索引、插件状态、最近一次运行记录,以及依赖安装目录。示例 Hook 配置:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/log-command.js\" --data-dir \"${CLAUDE_PLUGIN_DATA}\""
          }
        ]
      }
    ]
  }
}

写插件脚本时,最容易犯的一个错误,是随手在源码同级目录新建一个 cache/ 或 index.db。接下来会发生什么?插件发布新版本、用户升级时,原来的安装目录会被新文件整个替换。你为用户建立好的本地搜索索引、会话状态或缓存文件随之消失,用户下一次启动又得从头把整个工程扫一遍。

所以要分清两个内置变量的职责:

  • CLAUDE_PLUGIN_ROOT 只用来存放随版本分发的静态代码、预置模板和只读脚本,不往里面写运行时数据。
  • 需要跨版本保留的本地缓存、构建索引或者用户偏好,写进 CLAUDE_PLUGIN_DATA 指向的持久目录。

按 Claude Code 的插件清单参考,这个目录位于 ~/.claude/plugins/data/<id>/,首次引用时创建,插件更新时保留。把运行数据与静态源码分开存放,插件经历多次版本迭代后,用户的体验才不会三天两头被打断。

卸载就是另一回事了。官方说明,插件从最后一个安装位置卸载时,默认会删除这个数据目录。哪些数据能重建、哪些需要留下,先分清,再看卸载选项并备份。

03 发布前,按实际加载路径检查

发布前先从本地路径加载插件,验证真实安装效果:

claude --plugin-dir ./my-plugin
claude --plugin-dir ./my-plugin --plugin-dir ./another-plugin

按实际加载路径检查:

检查层要看到的结果
清单和入口plugin.json 合法,命令列得出、调得动
代理与方法agents 能发现,skills 按需触发
自动化与服务hooks 时机正确,MCP 连得上,LSP 不阻塞启动
材料与配置模板路径正确,脚本可执行,userConfig 读得到
敏感内容已标记,不进源码、日志或版本库

再运行一次静态校验:

claude plugin validate ./my-plugin

官方把 claude plugin validate 作为清单的权威检查,它会核对各组件路径是否存在、是否越出插件目录。校验通过后,再选一条低风险命令走完实际流程。它读得到模板吗?需要的 Hook 真触发了吗?外部服务连不上时,结果会清楚说明失败吗?静态校验查配置,实际调用再验证组件之间是否接得上。

04 升级:代码和数据是两条线

升级按这个顺序准备:

记旧版本与 DATA 路径,确认旧版本可取得
  → 保存旧数据,或说明哪些可以重建
  → 升级并运行常用、带参数的调用
  → 比较字段、格式、参数解析与行为
  → 不兼容就先修,尚未解决不向团队推广

按下升级之前,顺着最坏的情况多推演一步:万一新版本大面积报错,只把插件代码退回旧版,整个工作流就能复原吗?回到开头的例子,很可能不行。新版本的初始化脚本已经把 CLAUDE_PLUGIN_DATA 里的缓存库迁移成新结构,代码降回去,旧版逻辑读到新格式的文件,照样崩溃。

所以插件的升级预案,要把“代码版本”和“持久数据”当成两条独立的线来看待:

  • 代码这条线:版本清晰打标,确保随时能取到上一个稳定版本的安装包;
  • 数据这条线:升级前对持久数据目录做快照备份,或者在设计之初就让新版能读旧格式、旧版能识别并跳过新格式。

只记录版本号还不够。升级前确认数据能够回滚,再向团队推广大版本。

05 外部插件按未知代码对待

插件聚合了自动执行、外部连接和写入,沿整条组件链检查:

  • 来源与说明:可信来源、依赖和安装说明、版本记录。
  • 执行范围:自动 Hook、代理工具、敏感 MCP,以及系统或生产写入。
  • 配置:敏感 userConfig 标记与真实使用过程。
  • 恢复:可禁用、可卸载,并有回滚办法。

在完成工具审查之前,任何外部引入的插件包,都应视作一组拥有本地执行权力的未知代码。把文件装成一个包,不会自动为这些操作建立独立沙箱。插件里的子代理确实被限制了部分字段(hooks、mcpServers、permissionMode 会被忽略),防止插件代理在安装后隐式获得过高控制权;但运行环境、工具权限和外部账号仍需各自收紧。

06 插件问题按层排查

遇到问题时,按现象分层检查。

插件无法安装,检查:

  • Claude Code 版本是否支持该插件能力。
  • plugin.json 是否是合法 JSON。
  • 插件目录结构是否正确。
  • 当前用户是否有读取权限。
  • 本地路径是否正确。

组件没有加载,检查:

  • plugin.json 中声明的路径是否存在。
  • commands 是否放在正确目录。
  • agents frontmatter 是否有效。
  • skills 是否包含 SKILL.md。
  • hooks 配置事件名是否正确。

安装后命令不可用,用 /plugin 确认插件已安装并启用,必要时执行 /reload-plugins。它会重新读取清单、commands、agents、skills、hooks 和 MCP/LSP 配置,便于在当前会话中验证修改。

MCP 连接失败,检查:

  • MCP server 是否安装。
  • 环境变量是否存在。
  • 凭据是否有效。
  • endpoint 是否可达。
  • 插件内 .mcp.json 是否路径正确。

Hook 执行问题,检查:

  • 脚本是否有执行权限。
  • Hook 事件名是否正确。
  • matcher 是否匹配。
  • 脚本是否能读取 stdin JSON。
  • 错误是否写到 stderr。

插件何时打包、目录和版本号怎么定,见 Claude Code 配置什么时候该做成插件。命令和模板同样要把真实凭据留在文件之外,有副作用的命令怎样设边界,见Claude Code 只读命令为什么还能改文件。

代码可以随版本号来回切换,数据只会往前走。升级预案里给数据留一条退路,降级才真的能把工作流带回原来的样子。