Vivi 团队过去半年踩过的所有重要坑,按主题分类。每条包含"现象"、"根因"、"今后怎么做"三段。新人读完省一周排查时间。
现象:用户偶发 401。根因:Telegram.WebApp.initData 在模块顶层捕获时是空字符串(脚本加载时机早于 Telegram SDK 初始化)。
今后:在 api.js 的请求函数内每次现读,禁止在模块顶层 const initData = ...。
现象:分享 story 链接给已用过 Bot 的用户,点进去不跳目标 story。根因:Telegram 客户端对已建会话的 Bot 不再处理 ?start= 参数。
今后:站内 story 跳转用 location.hash,禁止用 openTelegramLink('https://t.me/Bot?start=xxx')。
现象:新版 Telegram 客户端调用偶发认证失败。根因:Telegram 8.0+ 在 initData 中加了 signature 字段并纳入 HMAC 计算,老的"pop signature"代码会破坏校验。
今后:auth 校验时不要 pop signature,把它和其他字段一起参与签名计算。
现象:输入框被键盘遮挡 / 高度跳动。根因:Telegram WebView 不支持 position: fixed、scrollIntoView、100vh。
今后:用 Telegram 提供的 --tg-viewport-height CSS 变量做高度定位,避开上述 web 默认方案。
现象:付费漏斗看不到取消率,转化率虚高。根因:Telegram 会在 client 多次回调 openInvoice,且取消行为只能通过回调 status 知道。
今后:callback 必须埋 invoice_callback 事件(status: paid / cancelled / failed),同一笔支付要去重,否则会重复计数(已修过 #103)。
现象:合并 PR 后 30 秒内生产环境出问题。根因:Vivi repo 没有 PR 级 CI hook,只有 push 到 main 触发的部署 workflow。
今后:合并 PR 前手动跑测试。改动较大时先在本地起服务 + 跑 pytest,再合并。
现象:说"已部署"实际测试失败 CI 静默阻断。根因:测试失败时 deploy job 不会跑,但 git push 已成功,从客户端看似没问题。
今后:push 后 gh run list --limit 1 → 等 success → 再 curl 生产文件验证内容。CI 绿不等于 CDN 已刷新。
现象:生产代码与 GitHub main 脱节,排查 bug 时怎么都对不上。根因:flyctl deploy 直接从本地推 image,不会经过 GHA。
今后:统一走 git push → GHA 自动部署。排查问题第一步 SSH 进容器看实际 commit hash,确认服务器版本。
现象:CI 绿了但用户看不到改动。根因:CF Pages 内容哈希不变时不刷新 CDN(短时间内连续部署同样内容)。
今后:webapp/index.html 的 ?v=N 必须 bump,再 curl https://vivi-app-e7m.pages.dev/index.html | grep "?v=" 验证。
现象:带中文的 commit 推上去,CF Pages 没触发部署。根因:CF Pages 的 GHA action 解析失败,整个 workflow 静默挂掉。
今后:触发部署的 commit message 必须用 ASCII。中文描述放 PR description / commit body 第二段。
现象:生产前端跨域请求被拒。根因:原 regex 写死了 hash 前缀子域名,不匹配主生产域名。
今后:FastAPI CORS regex 用可选组兼容两种域名形态:vivi-app(?:-[a-z0-9]+)?\.pages\.dev。
现象:CI 跑 pytest 起步就炸。根因:Pydantic Settings() 初始化时缺 required 环境变量直接 raise。
今后:CI workflow 里 TELEGRAM_BOT_TOKEN=test_token_for_ci、OPENROUTER_API_KEY=test_key_for_ci。共享 .env 时 Settings 必须 extra="ignore"。
现象:SSH 进容器查 SQLite 文件查不到生产数据。根因:Vivi 已迁移到 Turso 云数据库,本地 SQLite 只在 dev fallback 时才用。
今后:查数据走 turso db shell vividreams,不要 SSH 后 sqlite3 /app/data/...。
现象:把数据写到 /data/,重启后丢失。根因:Vivi 的 volume mount destination 是 /app/data,不是 /data。
今后:所有持久化代码(Mem0 Qdrant、上传文件)路径必须查 fly.toml 的 [[mounts]]。Vivi 是 /app/data。
现象:改完 app name 后 GHA 部署直接 401。根因:Fly.io deploy token 权限绑在原 app。
今后:fly tokens create deploy -a <new-app> → 更新 GitHub Secret FLY_API_TOKEN。
现象:改了 energy 默认值,CI 5+ 个测试爆炸要逐个修。根因:测试断言里硬编码了旧默认值。
今后:改默认值前先 grep -rn "energy.*30" tests/,一次性更新所有断言。
现象:新代码读老表的字段,报 no such column。根因:CREATE TABLE IF NOT EXISTS 不会修改已存在的表。
今后:新增字段必须写自动迁移:PRAGMA table_info 检查 → ALTER TABLE ... ADD COLUMN。
现象:2026-04-13 OpenRouter 突然 403 拒绝调用,理由是 "violation of provider Terms Of Service"。根因:Vivi 的提取任务(亲密度分析、记忆 LLM)用了 Gemini Flash,输入上下文含 NSFW 字符串触发 Google TOS 封禁。
今后:聊天 / 提取 / Mem0 / 任何环节统一走非审查模型(OpenRouter 上的 sao10k/l3.3-euryale-70b + qwen/qwen-2.5-7b-instruct),禁止使用 Google / OpenAI / Anthropic 系列。
现象:OpenRouter API 突然 403。根因:有时是 API Key 级别(换 Key 即可),有时是账号级别(必须改模型)。
今后:403 第一步:登录 OpenRouter Dashboard 看 Logs 页区分。Key 级别 = revoke + 重建一个;账号级别 = 必须切非审查模型。
现象:2026-04-01 主模型 404 整个聊天功能挂掉,日志只有 "AI service error" 没有原因。根因:没 fallback、没记录 traceback。
今后:OpenRouter 调用必须带 fallback 模型;try/except 用 logger.exception() 记完整堆栈。LLM 调用失败必须退 1⚡ 给用户。
现象:PostHog 数据可能含成人对话内容,第三方拒绝服务风险。根因:原始埋点把消息正文当属性传了。
今后:事件属性只传元数据:message_length / message_index / char_name,禁止传 content。
现象:用 fluently-xl 生女性写实图片,结果出动漫风。根因:模型名不带 "anime" 但实际是动漫训练。
今后:写实路径用 lustify-v8(NSFW)或 fal.ai FLUX Pro v1.1。换 Venice 模型前先看模型卡描述。
现象:生图 task 整个失败,用户扣完 15💎 看不到图。根因:face-swap 对 NSFW 图人脸不够清晰常返回 422,raise_for_status() 直接抛错。
今后:422 时降级返回 base 图(用户能看到模糊预览也比报错好)。禁止对 face-swap 错误 raise。
现象:Venice.ai 返回的"URL"传给 fal.ai face-swap,超时 111s 后 422。根因:Venice response_format="url" 实际仍返回 data URI(base64),fal.ai 处理不了。
今后:拿到 URL 先检测 data: 前缀;NSFW 主路径改走 fal.ai FLUX Pro v1.1(返回真 URL),Venice 仅作 fallback。禁止对 data URI 二次 JPEG 压缩(双重压缩明显模糊)。
现象:改了 views/chat.js 但生产无变化。根因:Vivi 前端无构建步骤,页面只加载 webapp/js/bundle.js,views/*.js 是源文件未被加载。
今后:改完 view 必须手动同步到 bundle.js 对应位置。建议:在两份代码顶部都加注释 "if you change one, change the other"。
现象:路由跳转中触发自动跳转,页面 DOM 错乱。根因:异步 route() 之间互相覆盖 DOM。
今后:路由器维护 _routeVersion 计数器,每次 route 时自增;异步 callback 完成后比对版本号决定是否更新 DOM。
现象:聊天最后一句偶发缺字。根因:SSE reader 循环 done=true 时 buffer 里还有最后一条事件未处理就 break 了。
今后:break 前先把 buffer 残余按 \n\n 分一次。
现象:切换聊天后输入框锁死无法发送。根因:切页时 sending=true 状态没清理。
今后:每次 render() 必须 sending=false,长生命周期引用提升为模块级变量(render 内局部变量不可被异步回调安全捕获)。
现象:PostHog 事件大量丢失,前端 stub 看似存在但没真发出去。根因:Telegram WebView 内大量装广告拦截器,三层拦截(DNS / 指纹 / JS 内部)只反代解决第一层。
今后:① 反代 /phog/*;② 服务端 SDK 兜底转发(事件带 $source: "server");③ sendBeacon + pagehide。三层兜底缺一不可。
现象:匿名用户事件全部丢失。根因:默认 identified_only 会丢未 identify 的事件,而 Telegram 用户在 onboarding 完成前是匿名状态。
今后:PostHog init 用 person_profiles: 'always'。
现象:监控查询永远返回 0。根因:前端打了 plan_subscribe,监控脚本查 subscribe_plan。
今后:新增监控查询时同时确认前端已 capture 该事件。两者必须配对上线,否则永久 0。
现象:看 PostHog 数据像 Web 产品而非 Telegram。根因:PostHog Project ID 配错(368707 是 Softie,377981 才是 Vivi)。
今后:数据反常时三重交叉验证(Bot API + 渠道能力 + 支付通道)。Vivi 的所有埋点配置 project_id 必须 377981。
现象:从 Softie 拆出 Vivi 时只改了前端 repo,后端、监控、CI 仍指向旧项目。根因:没做全链路审计。
今后:项目拆分 / 接手时必须列清单:前端 / 后端 / 监控(PostHog/Sentry/Clarity)/ CI workflow / Lark webhook / 域名 / DNS。一项一项核对。
现象:删了端点后线上正常,但 CI 大面积红。根因:测试里硬编码了旧端点路径。
今后:删 / 改端点前 grep -rn "/api/old-endpoint" tests/,连同测试一起改。
现象:文档里堆术语,PM 看不懂、工程不爱看。根因:没分层。
今后:面向 PM + 工程混合受众的文档(比如这份知识库)必须按 产品 → 协作 → 技术 分层递进,每层独立可读。
现象:群里通知 @all 噪音爆炸。根因:把 @all 当默认。
今后:所有推送只 @ 野荞(open_id 239e82d1),禁止 @all。表格 / 对比信息用 column_set 多列布局,禁止用 ASCII 图或 Markdown 表格(Lark 不渲染)。
现象:本地未 commit 的改动被 hook 静默 push 覆盖到远端。根因:Stop hook 自动 git add -A 把所有未追踪文件都吃进去。
今后:子 agent 自动 commit 必须在 prompt 中指定 commit message 或禁止自动 commit。Stop hook 不能加 git 操作。