【Bug已解决】Codex app upgrade loses Superpowers plugin installed state 解决方案

【Bug已解决】Codex app upgrade loses Superpowers plugin installed state 解决方案
【Bug已解决】Codex app upgrade loses Superpowers plugin installed state 解决方案原始报错线索Codex app upgrade loses Superpowers plugin installed state应用升级之后原本装好的 Superpowers 插件状态丢失了升级完插件像是没装过。一、现象长什么样用户装好了一个插件如 Superpowers用得好好的某天应用提示升级点完升级插件不见了功能消失设置里「已安装插件」列表空了重装插件又能用但升级又会再丢排查发现状态文件要么被新版本覆盖要么存在「应用包内部目录」升级即清。 根因是插件状态存错了地方 / 升级流程没做状态迁移更新把用户状态一并抹掉了。二、背景状态该存在哪应用有两类存储位置绝不能混位置用途升级时应用包内bundle / Program Files / /usr/lib程序代码、默认资源会被整体替换用户数据目录AppData / Library/Application Support / .config / .local/share用户状态、插件、配置应保留插件安装状态是用户状态必须存到用户数据目录而不是应用包内。应用升级只替换包内文件用户目录不动状态才保得住。三、为什么升级后丢失根因3.1 状态存进应用包目录插件状态写在Applications/App.app/Contents/...或C:\Program Files\App\plugins.json升级整体覆盖 → 没了。3.2 升级流程不迁移新版本换了状态文件格式/路径但升级程序没把旧状态搬到新位置直接用了空的新文件。3.3 状态无版本号状态文件没记录「格式版本」新版本读到旧格式要么忽略要么覆盖无法识别需迁移。3.4 原子性缺失升级中途崩溃状态文件写了半截 → 损坏 → 应用当「无状态」处理。四、最小可运行复现状态存错位置被覆盖下面演示「状态写在会被覆盖的目录」为何升级即丢import os, json # 错误把状态写进『应用包内目录』升级会整体替换 APP_BUNDLE_DIR /Applications/App.app/Contents/Resources/state def save_plugin_state_bad(installed): os.makedirs(APP_BUNDLE_DIR, exist_okTrue) with open(os.path.join(APP_BUNDLE_DIR, plugins.json), w) as f: json.dump({installed: installed}, f) def simulate_upgrade(): # 升级整个包目录被新版本覆盖 import shutil shutil.rmtree(APP_BUNDLE_DIR, ignore_errorsTrue) # 新版本包里没有用户旧状态 - 丢失 if __name__ __main__: save_plugin_state_bad([Superpowers]) simulate_upgrade() path os.path.join(APP_BUNDLE_DIR, plugins.json) print(升级后状态还在?, os.path.exists(path)) # False - 丢失状态写在包内升级一覆盖就没了。五、解决方案一状态存用户数据目录不被升级碰import os, sys, json def user_state_dir(app_nameApp): 用户级状态目录升级只换包不碰这里。 if sys.platform darwin: base os.path.expanduser(~/Library/Application Support) elif sys.platform win32: base os.environ.get(APPDATA) else: base os.environ.get(XDG_CONFIG_HOME) or os.path.expanduser(~/.config) p os.path.join(base, app_name) os.makedirs(p, exist_okTrue) return p def save_plugin_state(installed): d user_state_dir() with open(os.path.join(d, plugins.json), w) as f: json.dump({version: 1, installed: installed}, f) def load_plugin_state(): d user_state_dir() p os.path.join(d, plugins.json) if not os.path.exists(p): return {version: 1, installed: []} with open(p) as f: return json.load(f) if __name__ __main__: save_plugin_state([Superpowers]) # 模拟升级只换包目录用户目录不动 print(升级后状态:, load_plugin_state()) # 仍在 ✅状态进用户目录升级整体替换包时它安然无恙。六、解决方案二状态带版本号 升级迁移状态文件记录version升级时若版本旧则迁移到新格式绝不丢弃def migrate_state(old): 把旧版本状态迁移到当前版本保留已安装插件。 current {version: 2, installed: [], enabled: {}} # 旧版只有 installed 列表迁移时保留并补 enabled 默认 current[installed] old.get(installed, []) for name in current[installed]: current[enabled][name] True return current def load_with_migration(): state load_plugin_state() if state.get(version, 1) 2: state migrate_state(state) # 迁移后写回幂等 d user_state_dir() with open(os.path.join(d, plugins.json), w) as f: json.dump(state, f) return state if __name__ __main__: # 假设旧状态是 version 1 save_plugin_state([Superpowers]) # 写入 version 1 # 升级后加载自动迁移到 version 2插件保留 print(load_with_migration())versionmigrate保证升级不丢数据旧格式平滑过渡解决 3.2/3.3。七、解决方案三原子写 升级保留验证状态写入用临时文件 原子替换且升级完成后做「状态保留校验」import os def atomic_save(path, data): tmp path .tmp with open(tmp, w) as f: json.dump(data, f) os.replace(tmp, path) # 原子避免半截文件 def verify_after_upgrade(expected_installed): 升级后检查状态是否保留缺失则告警/从备份恢复。 state load_plugin_state() missing [p for p in expected_installed if p not in state[installed]] if missing: print(f[升级] 警告以下插件状态疑似丢失: {missing}) return False return True if __name__ __main__: save_plugin_state([Superpowers]) atomic_save(os.path.join(user_state_dir(), plugins.json), load_plugin_state()) verify_after_upgrade([Superpowers]) # True原子写防损坏升级后校验防静默丢失解决 3.4。八、跨平台与发布要点绝不写包内任何用户状态都走用户数据目录备份升级前备份状态文件失败可回滚多版本共存若支持多版本并行状态按版本或向前兼容设计权限用户目录权限要对别因 ACL 写不进可观测升级日志打印「保留/迁移了哪些插件」。九、排查清单「升级后丢插件状态」按下面排查状态存哪了是否在应用包内升级会覆盖第五节升级是否迁移状态旧格式是否被搬到新位置第六节状态有版本号吗新版本能否识别旧格式第六节写入原子吗半截文件会损坏第七节升级后校验吗丢失能否告警第七节有备份吗升级前是否备份可回滚权限够吗用户目录是否写不进日志是否记录保留/迁移。十、小结「应用升级后丢失插件安装状态」的根因是插件状态存进了会被升级覆盖的位置且升级流程不做状态迁移。通用修复存对地方状态进用户数据目录AppData / Application Support / .config升级只换包不碰它带版本 迁移状态文件记version升级时旧格式自动迁移保留第六节原子写 校验临时文件 os.replace防损坏升级后校验保留第七节备份可回滚升级前备份状态失败可恢复。 一句话用户状态永远不能住在「应用包」里它必须落在升级碰不到的用户目录且每次升级都要把旧状态迁移到新格式而非丢弃。把状态当成「比代码更持久的东西」来对待升级就再也不会让用户「装好的插件一夜消失」——这与第 116 篇路径规范、第 123 篇版本锁定、第 130 篇回退共同体现「用户状态必须持久、可迁移、不被升级破坏」。