结论

desktop-notifications = false 能禁用你看到的 Codex 完成通知,是因为这类通知通常走的是终端控制序列(尤其是 OSC 9)到 Ghostty,再由 Ghostty 转成系统通知。

机制拆解

  1. Codex 侧(TUI)

    • OpenAI 配置文档里 tui.notification_method 支持 auto | osc9 | bel,用于“非聚焦终端通知”。
    • tui.notifications 控制是否启用 TUI 通知。
    • 这意味着 Codex TUI 会向终端输出通知序列(osc9)或响铃字符(bel)来提醒。
  2. Ghostty 侧(终端)

    • desktop-notifications = true 时,Ghostty 会允许终端内应用通过 OSC 9(以及 OSC 777)触发桌面通知。
    • 如果设为 false,这类应用级桌面通知会被禁用。
  3. Bell 路径(备选)

    • BEL (0x07) 也是一种“提醒用户”的终端控制字符。
    • Ghostty 对 BEL 的具体效果受 bell-features 配置控制(声音、attention、标题提示等)。
  4. Codex 的额外通知能力(不是 Ghostty 专属)

    • Codex 还支持 notify 配置:任务完成时执行外部命令,并附带 JSON payload。
    • 开源实现可见 legacy_notify.rs:在 after_agent 事件触发后,spawn 外部命令并传入通知 JSON。

可直接用的 Shell 触发命令

1) 触发 Ghostty 桌面通知(OSC 9)

printf '\e]9;Codex task finished\a'

等价 ST 终止形式:

printf '\e]9;Codex task finished\e\\'

2) 触发 Bell(由 bell-features 决定表现)

printf '\a'

3) 用 Ghostty 的“命令结束通知”测试(非 Codex 专属)

在 ~/.config/ghostty/config 里设置:

notify-on-command-finish = always
notify-on-command-finish-action = notify,no-bell
notify-on-command-finish-after = 1s

然后执行:

sleep 2

注意:Ghostty 文档说明该功能依赖 shell integration 或 shell 发送 OSC 133 命令边界标记。

为什么你的设置生效

你实测 desktop-notifications = false 后,Codex 完成通知消失,这与 Ghostty 文档行为一致:应用通过 OSC 9/777 发来的桌面通知会被终端层拦截。

相关配置片段(Codex)

[tui]
notifications = true
notification_method = "auto" # 可选: "osc9" | "bel"
# 可选:走外部命令通知(与 Ghostty 无关)
notify = ["bash", "-lc", "echo \"$1\" >> /tmp/codex-notify.log"]

2026-03-24 排查命令与结论

A. Ghostty 通知相关默认项确认

命令:

ghostty +show-config --default | rg -n "notify-on-command-finish|notify-on-command-finish-action|notify-on-command-finish-after|desktop-notifications|bell-features|app-notifications"

输出要点:

  • notify-on-command-finish = never
  • notify-on-command-finish-action = bell,no-notify
  • notify-on-command-finish-after = 5s
  • bell-features = no-system,no-audio,attention,title,no-border
  • desktop-notifications = true

结论:

  • Ghostty 默认允许应用通过 OSC 9/777 发桌面通知(desktop-notifications = true)。
  • 命令结束通知与 Bell 配置是另一条机制,不能替代 desktop-notifications。

B. 为什么 ghostty +show-config | rg ... 没结果

命令:

ghostty +show-config | rg -n "notify|notification|bell-features|desktop-notifications|app-notifications"

输出要点:

  • 无输出,退出码 1。

命令:

ghostty +show-config --help

输出要点:

  • 默认只显示“与默认值不同”的项(changes only)。

结论:

  • 没输出不代表配置项不存在,只表示当前用户配置没改这些键。

C. 用户配置文件与语法校验

命令:

if [ -f ~/.config/ghostty/config ]; then nl -ba ~/.config/ghostty/config | sed -n '1,200p'; else echo "NO_CONFIG"; fi

输出要点:

  • NO_CONFIG(当时不存在用户配置文件)。

命令(语法验证):

cat >/tmp/ghostty-test.conf <<'EOF'
desktop-notifications = false
notify-on-command-finish = never
notify-on-command-finish-action = bell,no-notify
EOF
ghostty +validate-config --config-file=/tmp/ghostty-test.conf

命令(验证 no-bell 合法):

cat >/tmp/ghostty-test2.conf <<'EOF'
notify-on-command-finish-action = no-bell,no-notify
EOF
ghostty +validate-config --config-file=/tmp/ghostty-test2.conf

命令(验证 bell-features = false 合法):

cat >/tmp/ghostty-bell-false.conf <<'EOF'
bell-features = false
EOF
ghostty +validate-config --config-file=/tmp/ghostty-bell-false.conf

输出要点:

  • 均可通过校验。

结论:

  • Ghostty 语法层面支持上述写法,问题不在“配置拼写错误”。

D. Codex 本地实现路径确认(命令侧)

命令:

which codex
file /usr/local/bin/codex
head -n 60 /usr/local/bin/codex

输出要点:

  • /usr/local/bin/codex 是 Node 启动器,真实二进制在 npm 包 vendor 目录。

命令:

BIN=/usr/local/lib/node_modules/@openai/codex/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/codex/codex
file "$BIN"
strings "$BIN" | rg -in "osc9|notify|notification|bell" | head -n 200

输出要点:

  • 二进制字符串可见 osc9、legacy_notify.rs、notification_method 等关键词。

结论:

  • Codex TUI 存在终端通知路径(osc9/bel)和外部通知 hook 路径(legacy notify)。

E. Bash Tab 小铃铛(BEL)定位

命令:

bash -lc "bind -V" | rg -n "bell-style|show-all-if-ambiguous|completion-query-items|visible-stats"

输出要点:

  • bell-style is set to 'audible'

结论:

  • 在 Bash/Readline 下,按 Tab 出现铃铛通常来自 Readline 发 BEL,不是 Codex TUI。

F. 热加载 vs 重启生效(你的实测)

你实测:

  • bell-features = no-system,no-audio,no-attention,no-title,no-border
  • Ctrl+Shift+,(reload_config)后不生效
  • 重新打开 Ghostty 生效

结合命令:

ghostty +show-config --default --docs | rg -n "keybind = ctrl\\+shift\\+,=reload_config|config-reload|bell-features|desktop-notifications"

输出要点:

  • 默认确有 ctrl+shift+,=reload_config
  • GTK 有 config-reload 提示项
  • bell-features 和 desktop-notifications 都是有效配置键

结论:

  • reload_config 触发了配置重载动作,但并不保证所有行为在当前 tab/window 立刻切换。
  • 对你这个场景,重开 Ghostty 生效是可复现且可靠的应用方式。

G. 本轮最终结论

  1. Codex 完成通知被 desktop-notifications = false 禁用,符合 Ghostty OSC 9/777 机制。
  2. Bash 下 Tab 小铃铛主要是 Readline BEL;应在 shell 侧关 bell(如 set bell-style none)更直接。
  3. bell-features 在你环境里“热加载不即时、重启生效”,属于实测稳定行为,故建议改后重启 Ghostty 验证。

H. 辅助命令清单(完整性记录)

以下命令用于补充验证或定位路径,结论已并入前述章节:

rg -n "notification|notify|bell" /usr/share/doc /usr/share/man 2>/dev/null | head -n 80
ghostty +show-config | head -n 40
ghostty +show-config --default | nl -ba | sed -n '320,350p'
ghostty +show-config --default | nl -ba | sed -n '596,612p'
gsettings list-recursively com.mitchellh.ghostty | rg -n "notify|notification|bell|desktop"
ghostty +help
ghostty +validate-config --help
rg -n "bell-features|notify-on-command-finish|desktop-notifications" /usr/share/man /usr/share/doc 2>/dev/null | head -n 40
codex --help | head -n 80
codex --help | rg -n "notify|notification|bell|alert"
codex features --help | head -n 120
codex debug --help | head -n 120
strings /usr/local/bin/codex | rg -in "notify|notification|bell|osc|desktop|toast|alert|terminal bell|\\x1b\\]" | head -n 120
ls -la /usr/local/lib/node_modules/@openai
ls -la /usr/local/lib/node_modules/@openai/codex/node_modules/@openai/codex-linux-x64
find /usr/local/lib/node_modules/@openai/codex -maxdepth 4 -type f | head -n 200
find /usr/local/lib/node_modules/@openai/codex/node_modules/@openai/codex-linux-x64/vendor -maxdepth 4 -type f | sed -n '1,120p'
cat /usr/local/lib/node_modules/@openai/codex/node_modules/@openai/codex-linux-x64/package.json
nl -ba /usr/local/lib/node_modules/@openai/codex/README.md | sed -n '1,260p'
ghostty +show-config --default --docs | rg -n "bell-features|desktop-notifications|notify-on-command-finish|reload|config-reload"
ghostty +show-config --default --docs | nl -ba | sed -n '3360,3460p'
ghostty +show-config --default --docs | nl -ba | sed -n '3928,3978p'
ghostty +show-config --default --docs | nl -ba | sed -n '1360,1438p'
help bind
man bash | col -bx | rg -n "^ *bind |bell-style|Readline Init File|~/.inputrc|set bell-style|show-all-if-ambiguous|completion-query-items|set editing-mode" | head -n 120
man bash | col -bx | sed -n '3290,3415p'
man bash | col -bx | sed -n '4488,4568p'
man bash | col -bx | sed -n '6588,6645p'
man bash | col -bx | rg -n "Readline Init File|inputrc|readline initialization file|re-read-init-file|editing-mode"
man bash | col -bx | sed -n '3196,3270p'
man bash | col -bx | sed -n '4048,4070p'

I. bind 与 Readline 技术背景(补充)

I-1. 架构关系

在交互式 Bash 中,输入编辑和补全由 GNU Readline 负责:

  1. 你按键(如 Tab)。
  2. Readline 决定执行补全、编辑命令,必要时触发 BEL。
  3. 终端(Ghostty)收到 BEL 后,按自身配置决定是否显示铃铛提示/声音/attention。

因此:

  • 在 Readline 层关闭 bell(bell-style = none)通常最彻底;
  • 在 终端层(bell-features)只能控制“收到 bell 后如何表现”。

I-2. bind 是什么

bind 是 Bash 内建命令,用于操作 Readline:

  • 查看/修改键绑定;
  • 查看/修改 Readline 变量(如 bell-style);
  • 从文件加载配置(通常是 ~/.inputrc)。

bind 命令语法与 .inputrc 基本一致,区别是:

  • bind 'set bell-style none':当前 shell 立即生效;
  • ~/.inputrc:持久化配置,新交互 shell 启动时自动加载。

I-3. 与本问题直接相关的变量

  • bell-style(默认 audible):
    • none: 不响铃(Readline 不触发终端 bell)
    • visible: 可视化 bell(若终端支持)
    • audible: 听觉 bell
  • completion-query-items:候选过多时是否先询问。
  • show-all-if-ambiguous:补全歧义时是否立即展示候选。

I-4. 你这次验证结论

你实测 bind 'set bell-style none' 后,Bash 下 Tab 的 bell 被禁用,这与 Readline 机制完全一致,说明问题源头在 Readline bell 而非 Codex TUI。

I-5. 常用 bind 命令速查

# 查看当前 Readline 变量
bind -V

# 只看 bell 配置
bind -V | rg bell-style

# 当前会话立即禁用 bell
bind 'set bell-style none'

# 从配置文件重载
bind -f ~/.inputrc

持久化建议(~/.inputrc):

set bell-style none

补充:在非交互 shell 中执行 bind -V 可能出现 line editing not enabled,这是正常现象。

参考