你跑過型別檢查嗎?最後一行印出 Success: no issues found,你就覺得程式裡叫的每個函式都存在、型別都對得上了。
8 月 31 日下午,AI 照一張工作單(交給 AI 的任務說明)修好三個問題,工作單要它收尾前跑一次 mypy .。它回報三個修正都完成,節錄測試和檢查的部分:
全測試 1824 passed 0 failed;ruff/mypy……通過
改動推上 GitHub 之後,CI(每次推上去自動跑的一輪檢查)在 Windows 電腦上跑的型別檢查紅了,也就是檢查失敗:
tests\test_usage_statusline.py:204: error: Module has no attribute "fork"
同一份程式,在我這台 Mac 上沒問題,換到 Windows 就有一個函式不存在。
usage 是我做的開源小工具,在終端機和 Mac 選單列上,顯示 Claude Code、Codex 等 AI 工具用了多少 token、花了多少錢。7 月 15 日起,它也有 Windows 版。
所以每次推上 GitHub,CI 會借 GitHub 的一台 Mac、一台 Windows 電腦,各跑一次 mypy。
mypy 是 Python 的型別檢查工具。它不執行程式,只把程式從頭讀一遍,檢查你叫的函式存不存在、傳進去的東西型別對不對。usage 開的是最嚴格的模式,設定檔裡寫的是 strict = true。
平常 AI 改完程式,工作單會要它自己跑一次 mypy .。AI 是在我這台 Mac 上工作的,所以這一次檢查,也是在 Mac 上跑。
os.fork() 是 Python 用來把正在跑的程式複製一份的函式。Mac 和 Linux 有,Windows 沒有。
mypy 怎麼知道?它內建一份 Python 標準函式庫的型別說明,fork 在裡面是這樣寫的:
if sys.platform != "win32":
# Unix only
def fork() -> int: ...
sys.platform 是目前的作業系統。mypy 讀到這段,會照「跑它的這台電腦」來判斷:在 Mac 上跑,fork 存在;在 Windows 上跑,fork 不存在。
所以 AI 在 Mac 上跑 mypy,看到的是 Mac 的標準。os.fork() 那一行在 Mac 上完全合法,它回報通過,沒有說錯。只是這份程式也要在 Windows 上跑,那邊的標準,它沒檢查過。

mypy 有一個選項 --platform,可以指定要照哪個作業系統檢查。
我把 8 月 31 日 CI 紅燈的那一版程式拿出來,在 Mac 上跑兩次:
$ mypy .
Success: no issues found in 220 source files
$ mypy . --platform win32
tests/test_usage_statusline.py:204: error: Module has no attribute "fork"
Found 1 error in 1 file (checked 220 source files)
第二次,就是 Windows 那台 CI 的紅燈:同一個檔、同一行、同一句錯誤。不用開 Windows,在自己的 Mac 上就抓得到。
usage 從 5 月開始寫到現在,從來沒有用過 --platform。
這一次錯在測試檔。那個測試本來就設定成在 Windows 上跳過,不會真的執行,只是 mypy 還是會檢查它。當天的修正,是在 os.fork() 那一行加上 # type: ignore,叫 mypy 忽略這一行的錯。只加這個,換 Mac 那台會不滿意:Mac 有 fork,這個「忽略」根本用不到,最嚴格模式會把用不到的忽略也當成錯。所以同一個修正裡,設定檔還替這個測試檔關掉了這一項檢查。Windows 那台的紅燈,從下午 2 點半多一直亮到晚上 8 點 10 分,才轉回綠燈。
同樣的事,7 月 25 日發生過一次,規模大得多。
那天加的是一個新功能:開一個桌面視窗,讓 Claude Code、Codex 等幾個 AI 在裡面輪流討論。推上去,Windows 那台的 mypy 印出 26 個錯誤:
discussion_cli.py:777: error: Module has no attribute "getpgid"; maybe "getpid"
discussion_cli.py:781: error: Module has no attribute "killpg" [attr-defined]
discussion_window.py:549: error: Name "NSApp" is not defined [name-defined]
...
Found 26 errors in 3 files (checked 156 source files)
getpgid 查出一群程式屬於哪一組,killpg 把這一組一起結束,這兩個也只有 Mac 和 Linux 有。這次錯的不是測試檔,是 usage 真的會執行的程式:討論逾時或被取消時,要結束那幾個 AI 的程式,用的就是 getpgid 和 killpg。如果在 Windows 上跑到這裡,就會叫到一個不存在的函式。
這一版我一樣拿出來,在 Mac 上跑兩次。一般跑,Success: no issues found in 156 source files;加上 --platform win32,Found 26 errors in 3 files,跟 CI 一樣。

這次的修法不只是叫 mypy 別管:程式多寫了一個 Windows 版的結束方式,改用 Python 內建、Windows 也有的 process.terminate() 和 process.kill(),執行時照作業系統選一個用。
那天早上,Windows 那台 CI 前後紅了三輪才轉綠。CI 是一步一步跑的,mypy 失敗,排在後面的測試就不跑;mypy 修好之後,測試才跑得到,又冒出新的失敗。
前面兩次,都是 Mac 上一般跑檢查不出來、Windows 那台 CI 抓到。反過來也發生過。
7 月 29 日,Windows 版的程式用了 ctypes.windll,這是 Windows 才有的系統介面。這次紅的是 Mac 那台 CI:
wintray.py:619: error: Module has no attribute "windll" [attr-defined]
現在就可以做一件事:在你的專案裡搜尋 sys.platform、os.name,或是 fork、windll 這類只有某個作業系統才有的東西。
有找到,你的程式很可能有「只在某種作業系統上成立」的部分。用 mypy 的話,把你會支援的作業系統各跑一次:
mypy . --platform win32
mypy . --platform darwin
mypy . --platform linux
darwin 是 Mac,linux 是 Linux。平常只跑一次的那個,就是你自己這台。
工作單裡有一格「做完怎麼算對」,Day 4、Day 8 到 Day 16 各在這格加了幾行。今天再加一行:
做完怎麼算對:
- (Day 4、Day 8 到 Day 16 加的幾行)
- 支援多個作業系統的專案:型別檢查每個作業系統各跑一次(mypy --platform),回報要寫明在哪個作業系統上跑的
今天的「通過」,只代表跑它的那台電腦。明天看一個沒有任何檢查在管的問題:usage 的繁體中文介面上寫著「项目」「时间」「来源」,一共 52 句是簡體字。其中 48 句是 5 月 24 日帶進來的,跟 Day 16 那批空白的日文、韓文翻譯是同一次改動。8 月 5 日才修掉,中間發了 138 個版本,沒有一個測試檢查字是繁體還是簡體。
os.fork 型別錯誤的改動:https://github.com/aqua5230/usage/commit/5ab4bd0--platform 選項:https://mypy.readthedocs.io/en/stable/command_line.html#platform-configurationsys.platform 判斷:https://mypy.readthedocs.io/en/stable/common_issues.html#python-version-and-system-platform-checks