Codex + ntfy 通知使用说明
如果你经常让 Codex 在终端里跑任务,给它接一个 notify 钩子会方便很多。每轮对话结束后,Codex 都可以把本轮摘要推送到 ntfy,这样手机和桌面端都能第一时间收到提醒。
下面这套配置适合本地 Codex CLI + ntfy。默认示例使用公共服务器 https://ntfy.sh,如果你有自建 ntfy,只需要把服务器地址改掉即可。
1. 前置条件
- 已安装并能正常使用 Codex CLI
- 本机可用
python3
- 已能访问 ntfy
如果你直接使用公共 ntfy.sh,建议把 topic 设成一串随机字符串,不要用过于简单的名字。
2. 写入通知脚本
先创建 ~/.codex 目录,再写入脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| mkdir -p ~/.codex
cat <<'PY' > ~/.codex/notify.py
import json import os import sys import urllib.error import urllib.parse import urllib.request
NTFY_SERVER = os.environ.get("NTFY_SERVER", "https://ntfy.sh").rstrip("/") NTFY_TOPIC = os.environ.get("NTFY_TOPIC", "codex-notify").strip() NTFY_TAGS = os.environ.get("NTFY_TAGS", "computer").strip() NTFY_TOKEN = os.environ.get("NTFY_TOKEN", "").strip() NTFY_PRIORITY = os.environ.get("NTFY_PRIORITY", "").strip()
def shorten(text: str, limit: int) -> str: text = " ".join((text or "").split()) if len(text) <= limit: return text return text[: limit - 3] + "..."
def header_safe(text: str, fallback: str) -> str: text = text.replace("\r", " ").replace("\n", " ").strip() try: text.encode("latin-1") return text except UnicodeEncodeError: return fallback
def main() -> int: if len(sys.argv) < 2 or not NTFY_TOPIC: return 0
try: notification = json.loads(sys.argv[1]) except json.JSONDecodeError: return 0
if notification.get("type") != "agent-turn-complete": return 0
last_msg = notification.get("last-assistant-message") or "Turn complete" inputs = notification.get("input-messages") or [] cwd = notification.get("cwd") or "" thread_id = notification.get("thread-id") or ""
title = header_safe( f"Codex: {shorten(last_msg, 72)}", "Codex notification", )
lines = [f"Assistant: {shorten(last_msg, 500)}"] if inputs: lines.append(f"User: {shorten(' '.join(inputs), 300)}") if cwd: lines.append(f"CWD: {cwd}") if thread_id: lines.append(f"Thread: {thread_id}")
body = "\n".join(lines).encode("utf-8") url = f"{NTFY_SERVER}/{urllib.parse.quote(NTFY_TOPIC, safe='')}" headers = {"Title": title}
if NTFY_TAGS: headers["Tags"] = NTFY_TAGS if NTFY_PRIORITY: headers["Priority"] = NTFY_PRIORITY if NTFY_TOKEN: headers["Authorization"] = f"Bearer {NTFY_TOKEN}"
request = urllib.request.Request(url, data=body, headers=headers, method="POST") try: with urllib.request.urlopen(request, timeout=10): return 0 except urllib.error.URLError as error: print(f"ntfy notify failed: {error}", file=sys.stderr) return 0
if __name__ == "__main__": raise SystemExit(main()) PY
chmod +x ~/.codex/notify.py
|
默认 topic 是 codex-notify,只是为了让示例能直接跑起来。真正使用时,最好通过环境变量换成你自己的随机 topic。
3. 配置 Codex
编辑 ~/.codex/config.toml,把 notify 放在顶层:
1 2 3
| model = "gpt-5.4"
notify = ["python3", "/Users/你的用户名/.codex/notify.py"]
|
这里有两个细节要注意:
notify 必须在顶层,不要写进别的配置段里
- 脚本路径建议写绝对路径,别偷懒直接写
~/.codex/notify.py
如果你不知道自己的绝对路径是什么,可以先执行:
1
| echo "$HOME/.codex/notify.py"
|
然后把输出结果填进 config.toml。
4. 订阅 ntfy topic
你可以任选一种方式订阅:
- 手机安装 ntfy App,然后添加同名 topic
- 浏览器直接打开
https://ntfy.sh/你的topic
如果你后面通过 NTFY_TOPIC 改了 topic,订阅时也要用改过的那个名字。
5. 测试通知
先手动调用脚本,确认 ntfy 本身没有问题:
1
| python3 ~/.codex/notify.py '{"type":"agent-turn-complete","last-assistant-message":"测试通知","input-messages":["你好"],"cwd":"/tmp","thread-id":"demo"}'
|
如果这一步能收到推送,再跑一条最短的 Codex 会话:
正常情况下,Codex 完成这一轮后,你应该会再收到一条 ntfy 通知。
6. 可选环境变量
如果你不想把服务器地址、topic 和鉴权信息写死,可以在启动 Codex 前设置环境变量:
1 2 3 4
| export NTFY_SERVER="https://ntfy.sh" export NTFY_TOPIC="codex-2f8fcd2a2f204f1a" export NTFY_TAGS="computer" export NTFY_PRIORITY="default"
|
如果你的 ntfy 服务器启用了鉴权:
1
| export NTFY_TOKEN="你的token"
|
如果想长期生效,就把这些环境变量写进 ~/.zshrc 或 ~/.bashrc。
7. 常见问题
收不到通知
优先按这个顺序排查:
- 确认
notify 确实写在 ~/.codex/config.toml 顶层
- 确认脚本路径是绝对路径,而且文件有执行权限
- 先手动执行一次
python3 ~/.codex/notify.py '...'
- 再检查 topic 是否和 ntfy 订阅端一致
通知标题里中文不显示
这是 HTTP 头部编码限制导致的,正文不受影响。上面的脚本已经做了兜底处理:如果标题里有无法安全放进头部的字符,就自动退回英文标题 Codex notification。
公共 topic 不安全
ntfy.sh 的公共 topic 不是私密空间。只要别人知道你的 topic 名称,就可能订阅到同样的消息。更稳妥的做法有两种:
- 使用足够长、足够随机的 topic
- 自建 ntfy 服务,或者开启鉴权后配合
NTFY_TOKEN 使用