Skip to content

Codex + ntfy 通知使用说明

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
#!/usr/bin/env python3
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 会话:

1
codex "只回复 test"

正常情况下,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. 常见问题

收不到通知

优先按这个顺序排查:

  1. 确认 notify 确实写在 ~/.codex/config.toml 顶层
  2. 确认脚本路径是绝对路径,而且文件有执行权限
  3. 先手动执行一次 python3 ~/.codex/notify.py '...'
  4. 再检查 topic 是否和 ntfy 订阅端一致

通知标题里中文不显示

这是 HTTP 头部编码限制导致的,正文不受影响。上面的脚本已经做了兜底处理:如果标题里有无法安全放进头部的字符,就自动退回英文标题 Codex notification

公共 topic 不安全

ntfy.sh 的公共 topic 不是私密空间。只要别人知道你的 topic 名称,就可能订阅到同样的消息。更稳妥的做法有两种:

  • 使用足够长、足够随机的 topic
  • 自建 ntfy 服务,或者开启鉴权后配合 NTFY_TOKEN 使用

About this Post

This post is written by KaranocaVe.

#Codex #ntfy #通知 #CLI