【Bug已解决】Codex App crashes after typing `@` in the new-conversation composer 解决方案
·
【Bug已解决】Codex App crashes after typing @ in the new-conversation composer 解决方案
原始报错线索:Codex App crashes after typing
@in the new-conversation composer(在新会话输入框里刚打出@这个字符,应用就崩溃了)。
一、现象长什么样
用户在新会话输入框敲下 @:
- 应用立刻闪退 / 白屏 / 无响应;
- 崩溃栈指向「提及补全(mention completion)」相关代码;
- 有时只在「还没有任何会话 / 列表为空 / 网络未加载完」时打
@才崩,正常会话里没事; - 崩溃前往往没有任何错误提示,用户只是想 @ 个人/文件。 根因是触发字符的处理路径在「边界状态」下缺少防御,一旦
@触发时依赖的数据还没就绪或为空,就空指针/越界崩溃。
二、背景:提及补全是怎么工作的
现代编辑器/聊天框在输入 @ 后,会:
- 检测触发:监听输入,发现
@且后面在「词起始位置」; - 收集候选项:从「会话成员 / 文件列表 / 命令表」拉取可 @ 的对象;
- 渲染浮层:把候选项画成下拉列表;
- 过滤:随用户输入实时过滤;
- 选择:上下键 + 回车插入。 崩溃几乎都发生在第 2、3 步:候选项源为空 / 未初始化 / 异步加载中。
三、为什么打 @ 会崩:根因
3.1 空数据解引用
候选列表 None 或空,代码却直接 candidates[0] / candidates.length 当非空处理 → 越界/空引用。
3.2 异步未就绪就渲染
候选来自异步加载(网络/数据库),@ 触发时 future 还没返回,代码直接读 .result() 阻塞或读空 → 崩。
3.3 触发检测误判
在「字符串中间」而非「词首」打 @(如邮箱 a@b.com)也触发补全,后续逻辑假设「@ 后是要补全的名字」,解析出错。
3.4 异常未捕获
补全逻辑任何一步抛异常都没被 try/except 包住,异常一路冒泡到主线程 → 应用崩。
四、最小可运行复现(空列表解引用崩溃)
下面演示「候选为空时直接取首项」如何崩溃:
def show_mention_popup(query, candidates):
# 错误:假设 candidates 永远非空且已就绪
top = candidates[0] # candidates 为 None/[] -> 崩
return f"高亮: {top['name']}"
if __name__ == "__main__":
try:
show_mention_popup("@", None) # 崩溃:NoneType 不可下标
except Exception as e:
print("崩溃:", type(e).__name__, e)
candidates=None 时 candidates[0] 直接抛 TypeError,若没捕获就崩应用。
五、解决方案一:触发检测要精确(避免误触发)
只在「词首 / 空白后」的 @ 才触发,邮箱等场景不触发:
def should_trigger_mention(text, caret):
"""判断 caret 处的 @ 是否应触发提及补全。"""
if caret <= 0 or text[caret - 1] != "@":
return False
# @ 前面必须是行首或空白/标点,避免匹配 a@b.com
prev = text[caret - 2] if caret >= 2 else ""
if prev and not prev.isspace() and prev not in "((【":
return False
return True
if __name__ == "__main__":
print(should_trigger_mention("你好 @", 3)) # True
print(should_trigger_mention("a@b.com", 2)) # False(邮箱)
精确触发避免「在错误上下文打 @ 也拉补全」导致的后续解析崩溃。
六、解决方案二:候选为空 / 未就绪的安全处理
补全逻辑必须接受「无数据」状态,绝不假设非空:
def show_mention_popup_safe(query, candidates):
"""安全渲染:候选为空/未就绪时,安静地不显示或显示『无匹配』。"""
if not candidates: # None / [] 都安全处理
return {"visible": False, "items": []}
items = [c for c in candidates if query.lower() in c.get("name", "").lower()]
if not items:
return {"visible": True, "items": [], "hint": "无匹配"}
return {"visible": True, "items": items[:10], "highlight": items[0]["name"]}
if __name__ == "__main__":
print(show_mention_popup_safe("@", None)) # 不显示,不崩
print(show_mention_popup_safe("@a", [{"name": "Alice"}]))
空/None 一律先判,绝不 candidates[0],崩溃根因消除。
七、解决方案三:异步候选加载的异常安全
候选来自异步源时,用 try/except 包住加载,失败就降级为空列表而非崩溃:
def load_candidates_async(session_id):
"""异步加载可 @ 对象;任何失败都返回空列表而非抛异常。"""
try:
# 真实环境:从会话成员服务 / 文件索引拉取
data = fetch_members(session_id) # 可能网络失败
return data or []
except Exception as e:
# 异常安全:记录日志,降级为空,绝不冒泡到 UI 线程
print(f"[mention] 加载候选失败,降级为空: {e}")
return []
def fetch_members(session_id):
# 示意:会话未就绪时返回 None
return None
if __name__ == "__main__":
cands = load_candidates_async("no-session-yet")
print("安全得到:", cands) # [] 而非崩溃
异步失败被吞并降级,输入 @ 时即便数据没加载完也不会崩(解决 3.2/3.4)。
八、解决方案四:整个触发路径包一层兜底
最外层用「防御性调用」,任何意外异常都就地捕获、记录,保证输入法字符永不导致应用退出:
def safe_on_text_changed(text, caret):
"""输入框文本变化的总入口:任何异常都不让应用崩。"""
try:
if should_trigger_mention(text, caret):
cands = load_candidates_async(current_session())
return show_mention_popup_safe(text[:caret], cands)
except Exception:
# 兜底:宁可不出补全,也不能崩应用
import logging
logging.exception("提及补全异常,已跳过")
return {"visible": False}
def current_session():
return None # 模拟未就绪
if __name__ == "__main__":
# 即便内部全错,这里也返回安全结构,应用不崩
print(safe_on_text_changed("hi @", 4))
try/except 兜底保证:输入处理这条路径,异常只能被记录,不能被传播到让进程退出——这是输入触发崩溃的终极防线。
九、排查清单
「输入 @ 崩溃」,按下面排查:
- 触发检测是否精确?是否误匹配邮箱等
@(第五节); - 候选为空/None 是否安全?是否直接
candidates[0](第六节); - 异步加载是否异常安全?失败是否降级为空(第七节);
- 整条路径是否包兜底 try/except?异常能否冒泡到主线程(第八节);
- 崩溃栈指向哪一步?收集候选还是渲染浮层;
- 是否只在『空会话/未加载』时崩?说明是数据未就绪问题;
- UI 线程是否被阻塞?异步
.result()同步等待会卡; - 日志是否记录触发上下文。
十、小结
「输入 @ 触发应用崩溃」的根因是提及补全的触发路径在边界状态(空数据 / 异步未就绪)下缺少防御,异常冒泡到主线程致崩。通用修复:
- 精确触发:只在词首/
@后空白处触发,避开邮箱等误触发(第五节); - 空安全:候选为
None/空时安静不显示,绝不下标解引用(第六节); - 异步降级:候选加载失败返回空列表,不抛异常(第七节);
- 整路径兜底:
try/except包住触发处理逻辑,异常只记录不崩溃。 一句话:任何「输入字符 → 拉起 UI 浮层」的路径,都必须假设「依赖数据可能为空、可能未就绪、可能失败」,并把异常锁死在这一层。输入法是用户最高频的操作,绝不允许一个@把整个应用带走——这与第 115 篇「认证钩子异常不越界」、第 92 篇「终端转义不误读为输入」,共同体现「用户输入路径必须异常安全」的铁律。

更多推荐



所有评论(0)