我在 Cursor 的 hook 裡加了一行 time.sleep(4),Agent 那一步就剛好慢了 4 秒。
原本今天要把模型(Jev)搬進 hook,讓它在每次改檔的時候判斷這次改動的性質。動手之前,我先回頭驗一句自己寫過、卻沒有證據的話。Day 9 的結尾我寫:「Cursor 每次寫檔都在等它回來。」
這句話決定了模型該放在哪裡:
兩種情況的設計完全不同,所以今天只做一件事:量。
今天我在這個專案開了一個 Agent 對話,讓它幫我整理這個系列的流量數據。這個專案為了診斷,在 hooks.json 的 15 個事件上都掛了 hook_diag.py,postToolUse 和 afterFileEdit 另外再掛一支 audit_edit.py。所以 Agent 做的每一件事,前後都留下了紀錄。
每一筆紀錄的 ts,是 hook 腳本開始跑的時間。payload 裡有 tool_use_id,可以把同一次工具呼叫前後的事件串起來。有了這兩樣,就看得出兩個事件之間隔了多久。
問題是,光看間隔,分不出那段時間是 Cursor 在等 hook,還是 Cursor 本來就要花這麼久。要分得出來,得讓 hook 故意變慢,看間隔會不會跟著變長。
我給探針定了三個條件:
加在 hook_diag.py 的就是這一段:
flag = Path(__file__).resolve().parent / "_sleep_probe.json"
if flag.exists():
cfg = json.loads(flag.read_text(encoding="utf-8"))
m = re.search(r'"hook_event_name"\s*:\s*"([^"]+)"', record.get("stdin_text", ""))
if m and m.group(1) in cfg.get("events", []):
probe_ms = float(cfg.get("ms", 0))
record["probe_sleep_ms"] = probe_ms # 睡了多久也記進紀錄
# ……照常把紀錄寫完之後才睡,所以 ts 記的還是 hook 開始的時間
if probe_ms:
time.sleep(probe_ms / 1000)
hooks 資料夾裡沒有 _sleep_probe.json,這段就什麼都不做。
旗標設成 {"ms": 4000, "events": ["afterShellExecution", "postToolUse"]},然後讓 Agent 跑兩次最簡單的指令 Get-Date。
一次 Shell 呼叫會依序經過四個事件:preToolUse → beforeShellExecution → 指令執行 → afterShellExecution → postToolUse。
我要看的是 afterShellExecution 到 postToolUse 這一段。這時指令已經跑完,模型也還沒拿到結果,中間只有 Cursor 自己。如果這一段變長,只可能是 Cursor 在等 hook。
20:49:10.192 preToolUse
20:49:10.809 + 616 ms beforeShellExecution
20:49:21.617 + 10808 ms afterShellExecution ← 探針睡 4000 ms
20:49:26.310 + 4693 ms postToolUse
這一段平常是 714 ms,那是這個對話前 20 次 Shell 呼叫的中位數。兩次探針分別是 4,693 ms 和 4,627 ms,比平常多了 3,979 ms 和 3,913 ms,就是那 4 秒。
為什麼選 4 秒?因為這一段平常的起伏不小。20 次裡,p90 是 1,472 ms,還有一次 7,591 ms 的極端值,原因我沒有追。探針睡得太短,會被正常的起伏蓋過去;睡 4 秒,比 p90 大了快三倍。
而且只看一次不夠。那一次 7.6 秒的極端值,就比探針的 4.7 秒還長。兩次探針都落在「714 加 4,000」附近,不是隨機亂跳,這才算數。
Cursor 會等 after 類的 hook 跑完,才往下走。
postToolUse 是最後一站。它之後,是 Agent 拿到工具結果、想好下一步、送出下一個工具。這一段一定混了模型產生回覆的時間,沒辦法像上一段那樣乾淨。
我本來想找一個乾淨的對照。紀錄裡,Cursor 的改檔工具有時會拆成「先 Read、再 Write」兩步,兩步之間沒有模型介入,平常只隔 0.9 秒左右。我把 postToolUse 設成睡 6 秒,對一個草稿檔做一次替換,想看 Read 的 postToolUse 會不會把後面的 Write 往後推。結果那一次它沒有拆成兩步,只記到一次 Write。想要的對照沒有出現。
只好退一步,直接比「postToolUse 開始 → 下一個工具的 preToolUse」:
postToolUse → 下一個工具的 preToolUse
沒有探針 4,381 / 4,633 / 4,763 ms
探針睡 4 秒 7,762 / 8,079 ms
探針睡 6 秒 10,019 / 9,298 ms
每次都差不多是「原本的 4.5 秒,加上睡掉的時間」。如果 Cursor 不等 postToolUse,有沒有探針都應該落在 4.5 秒上下。看起來,工具的結果要等 postToolUse 跑完,才會回到 Agent 手上。
這一段每種條件只有兩三次,又混了模型的時間,我先當成強烈的跡象,不當定論。
hook 不是旁觀者。它睡多久,Agent 就等多久。
放回原本的計畫:如果我在 postToolUse 裡呼叫模型,Agent 每改一次檔,就要停下來等模型回答一次。「在背景跑一跑、不影響使用」這種想像,在我這台機器上不成立。
至於要等多久、值不值得等,還得先知道幾件事:現在的 hook 已經讓每一步多花多少時間、hook 同時跑會不會出事,以及問一次模型要多久。這是接下來三天要量的。
先確認你的 Agent 會不會等你的 hook。十分鐘做得完:
import json, re, time 和 from pathlib import Path。events 填你想驗的事件,例如 afterShellExecution。挑段落的時候,挑中間沒有模型介入的那一段,例如 afterShellExecution 到 postToolUse。做完記得把探針從 hook 裡拿掉,不要只刪旗標檔。
afterShellExecution 那段跑了兩次探針,結果一致,但就是兩次。preToolUse、beforeShellExecution 能擋住工具,照理一定會等,但我沒量。hook_diag.py 已還原。明天把帳攤開:模型還沒放進去,現在這幾支 hook 已經讓每一步多等多久?讀一個檔只要 11 毫秒,猜猜前後的 hook 加起來要多久。
今天對應的威脅: T3(藉 Agent 之手規避控制)。會拖慢每一步的稽核,最後的下場通常是被關掉;關掉之後,事後就舉不出證。
實驗與程式: 2026-09-24 跑。探針加在 .cursor/hooks/hook_diag.py(實驗後已移除),時間線用 day10-lab/probe_report.py 攤開。資料是這個專案的 hook-diag.jsonl。Cursor 3.21.9。本篇沒有引用外部來源。