Skip to content

[AI审查][Bug] 未安装 pypinyin 时中文昵称 slug 全部退化为 "ex",创建第二个前任会静默覆盖第一个(数据丢失) #26

Description

@josephmqiu

Important

🤖 AI 代码审查声明:本 issue 由 AI 模型 Claude Fable 5(Anthropic,运行于 Claude Code)在一次完整代码审查中发现并撰写,经 @josephmqiu 授权提交。审查基于当前 main 分支 HEAD(commit c5ece53),下述复现步骤已在本地实际运行验证。

问题描述

pypinyin 在 requirements.txt 里被标注为可选依赖(README「依赖(可选)」一节同样如此)。但当用户没有安装它时,skill_writer.pyslugify() 会走 fallback 分支(skill_writer.py:77-89),该分支丢弃所有非 ASCII 字符

except ImportError:
    import unicodedata
    result = []
    for char in name.lower():
        if char.isascii() and (char.isalnum() or char in ("-", "_")):
            result.append(char)
        elif char == " ":
            result.append("_")
    slug = "".join(result)
...
return slug if slug else "ex"

对本项目的主要用户群(中文昵称)来说,任何纯中文昵称都会变成空字符串,最后统一退化为 "ex"

复现步骤(已实际验证)

在未安装 pypinyin 的环境下(或模拟 ImportError):

from skill_writer import slugify
slugify('小美')     # => 'ex'
slugify('阿静')     # => 'ex'
slugify('小美2号')  # => '2'
slugify('Amy')      # => 'amy'

实际输出:

slugify('小美') = 'ex'
slugify('阿静') = 'ex'
slugify('小美2号') = '2'
slugify('Amy') = 'amy'
slugify('小 美') = 'ex'

数据丢失链条

slug 冲突本身只是难看,真正的问题是它会静默覆盖create_skill() 全程使用 exist_ok=True 并无条件写入(skill_writer.py:130-185):

skill_dir = base_dir / slug
skill_dir.mkdir(parents=True, exist_ok=True)   # 目录已存在也不报错
...
(skill_dir / "memories.md").write_text(...)    # 直接覆盖
(skill_dir / "persona.md").write_text(...)     # 直接覆盖
(skill_dir / "SKILL.md").write_text(...)       # 直接覆盖
(skill_dir / "meta.json").write_text(...)      # 直接覆盖

组合起来的完整场景:

  1. 用户未安装 pypinyin(README 说它是可选的);
  2. 创建前任「小美」→ 写入 exes/ex/
  3. 之后创建前任「阿静」→ slug 同样是 ex「小美」的 memories.md、persona.md、meta.json 被静默覆盖,无任何提示、无任何存档
  4. 由于覆盖发生在 create 流程(不经过 update 的存档逻辑),versions/ 里也没有备份,数据无法找回。

对一个以「保存回忆」为核心卖点的项目来说,这是最不应该发生的一类 bug。

修复建议

三层防御,建议都加上:

  1. slugify 失败时显式报错,不要退化为 "ex"
if not slug:
    print("错误:无法从昵称生成 slug(中文昵称需要安装 pypinyin:pip3 install pypinyin),"
          "或请显式传入 --slug", file=sys.stderr)
    sys.exit(1)
  1. create_skill 拒绝覆盖已存在的目录(或提供 --force):
if skill_dir.exists():
    print(f"错误:{skill_dir} 已存在。请换一个 slug,或先执行删除/备份", file=sys.stderr)
    sys.exit(1)
  1. 文档层面:README 把 pypinyin 从「可选」改为「使用中文昵称时必需」。

如需要,我可以提交对应的 PR。


English TL;DR: with pypinyin uninstalled (it's documented as optional), slugify() strips all non-ASCII characters, so every Chinese-only nickname collapses to the slug "ex". create_skill() then uses exist_ok=True and unconditionally overwrites memories.md/persona.md/meta.json — creating a second ex silently destroys the first, with no warning and no archive. Verified locally at commit c5ece53. Suggested fixes: error out on empty slug, refuse to overwrite an existing skill dir, and document pypinyin as required for Chinese nicknames.

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions