← 返回作品集

"Good afternoon, good evening, and good night."

為什麼你的 AI 早上八點還在跟你說晚安

Claude Code UserPromptSubmit Hook Stop Hook MIT License Open Source

半夜十二點,你丟了句話給 Claude,沒收工,session 就晾在那。隔天早上八點你回來繼續,它劈頭就說:

AI: 已經很晚了,要不要先休息一下?

不是它在等昨晚沒睡的你——是它壓根不知道現在是早上八點。它活在自己的片場裡,時間停在你上次離開的那一刻。

"In case I don't see ya: good afternoon, good evening, and good night." ——《楚門的世界》,楚門每天固定講的招呼語,也是全片最後一句台詞

楚門的片場靠人工日夜燈光操控他對時間的認知,他毫無所覺地活在裡面,天天講著時間問候語,卻是全片場裡最搞不清楚真實時間的人。LLM 也一樣:它會講「早安」「晚安」這些跟時間有關的話,但完全沒有真正的時間軸。

機器只有「順序」,沒有「時間」

Claude Code 只在 session 開始注入一次日期,之後就永遠停在那一刻。這聽起來是個小問題,拆開來看其實牽動好幾件事:

只有順序,沒有時間差

模型知道 A 訊息在 B 之前,但不知道中間隔了 2 分鐘還是 2 天。

時間敏感的輸入會出錯

「一小時後再檢查」「今天的 deadline」這類任務,少了時間差,模型無法判斷現在該不該做、狀態是否已過期。

狀態變得曖昧

沒有時間戳當錨點,「剛剛」跟「很久以前」對模型沒有差別,該不該重新驗證的判斷失準。

人回頭考古也沒座標

想追一件事,記得是哪天卻沒有確切時間,沒辦法跟系統 log 對齊比對,等於在時間軸上盲找。

不是一個人的抱怨:GitHub 上反覆被提的需求

先動手做完 prompt-timestamp 之後,才回頭去 anthropics/claude-code 的 issue 區查證,發現這是個被提過很多次、一直沒被正式解決的需求:

Issue標題狀態留言數
#47160 Expose message timestamps to the model in Claude Code sessions Closed(Not Planned) 7
#32913 [FEATURE] Date/Time Injection into prompts Open(2026-03-10 起,無 maintainer 回應) 9
#56368 Ambient session-time awareness in Claude Code Closed(Duplicate,併入 #32913) 3
#49084 expose timestamps to Claude as structured data Open(討論最熱) 13
#73800 Long sessions drift days past the session-start date Open 1

還有 #56855、#66604、#66245、#75281、#68013 等多個同主題的 open issue,模式很清楚:最早的請求被 stale-bot 關掉、後面的被標記為 duplicate、剩下的持續開著沒人處理。

順帶一提:#73800 底下有一則留言正是我自己的帳號,內容是「elapsed-time hint」的建議——這篇文章的做法,其實是先解決了自己的問題,才發現社群早有共識,只是沒人做成一個乾淨的小工具。Hacker News 上也有一則對應的小討論:"Claude doesn't know what time it is"

方案一:prompt-timestamp(給 LLM 看)

一個 UserPromptSubmit hook,在每則訊息送出前注入:

date '+[%Y-%m-%d %H:%M %z | calculate time since previous stamp]'
→ [2026-07-10 08:03 +0800 | calculate time since previous stamp]

%z 時區 offset 解決時區歧義,不需要任何設定。尾巴那句提示不是算好的值——它是靜態文字,引導模型自己回頭看對話歷史裡的前一個時間戳,自己算出間隔。

零 state file

不寫任何狀態到磁碟,context window 本身就是狀態。

零依賴

一行 date 指令,沒有額外腳本,沒有第三方套件。

零網路存取

完全本機執行,不打任何 API。

幾乎免費

每則訊息成本約 10 tokens。

方案二:turn-stamp(給人看)

prompt-timestamp 解決的是模型端;但「回頭考古對話記錄」這件事,是人的痛點,不是模型的痛點——這個需求是獨立冒出來的,不是看了誰的做法才想到。turn-stamp 用一個 Stop hook,在每輪結束時印出:

printf '{"systemMessage":"⏱ %s"}\n' "$(date '+%Y-%m-%d %H:%M')"
→ ⏱ 2026-07-10 08:03(只顯示在 UI,不進 model context)

做到一半才發現,GitHub 上人氣最高的同類專案 zoharbabin/claude-code-message-timestamps(25★)已經做過同樣的事——概念上完全對,但實作比想要的重得多:

5 檔案 · ~179 行
zoharbabin/claude-code-message-timestamps
hooks.json + 4 個 shell script(含依賴檢查、時區解析)
1 個 hook 定義
turn-stamp
單行 printf,寫在 hooks.json 裡,無額外腳本、無依賴檢查

安裝

# 兩個 plugin 各自獨立,裝一個或兩個都可以
claude plugin marketplace add raychao-oao/prompt-timestamp
claude plugin install prompt-timestamp@prompt-timestamp
claude plugin install turn-stamp@prompt-timestamp
→ 重啟 Claude Code 生效(hook 在 session 開始時載入)

不想裝 plugin,也可以直接把對應的 hook 貼進 ~/.claude/settings.json(README 裡有完整 JSON),但不要同時裝 plugin 又貼設定,會重複顯示。

跨平台差異:同一招,不一定通用

這段是整個過程裡最有意思的部分——直覺上同一個做法應該可以複製到其他 CLI 工具,實際上其中一個工具的底層架構直接把這招弄壞了。

Codex CLI:機制相同,輸出格式不同——而且真的有 turn-stamp 等價物

Codex CLI 也有 UserPromptSubmit,但它要求輸出結構化 JSON,不能像 Claude Code 一樣直接吃 plain stdout:

# ~/.codex/hooks.json
date '+{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"[%Y-%m-%d %H:%M %z | calculate time since previous stamp]"}}'

一開始以為 Codex 不需要 turn-stamp 的等價物——反正 hook 輸出本來就會印到終端機,已經看得到。後來發現這個假設是錯的:Codex 一般 hook 的輸出並不會直接顯示給人看,要讓一行字真的浮出來,得靠一個技巧——用 continue:false 搭配 stopReason 掛在 Stop 事件上,Codex 才會把 stopReason 的內容渲染成一行可見的 stop: 訊息:

# Stop hook:借用 continue:false 讓 stopReason 真的顯示出來
date '+{"continue":false,"stopReason":"reply ended at [%Y-%m-%d %H:%M %z]"}'
→ • Stop hook (stopped)
→ stop: reply ended at [2026-07-10 18:20 +0800]

兩個 hook 分工跟 Claude Code 一致:UserPromptSubmit 的時間戳進模型 context,Stop 的時間戳是印給人看的。新增 Stop 這類事件後要重啟 Codex,舊 session 不一定會吃到新掛上去的事件類型。

Antigravity CLI(agy):模型端不用補,人看端直接撞牆

agy 的模型端完全不用補——它原生就在每則 prompt 的 <ADDITIONAL_METADATA> 裡帶「The current local time is: ...」。但人看端本來想直覺比照 Stop hook 印一行 ,結果直接撞牆:agy 的 TUI 每輪都會重繪畫面緩衝區,hook 印出的換行符或 ANSI 定位字元會弄亂 readline 游標,下一次重繪還會把那行直接洗掉。

最後放棄 hook 機制,改在 ~/.gemini/GEMINI.md(agy 的全域指令檔)寫兩條 Rule,讓模型自己在回覆最後一行印出時間戳:

# ~/.gemini/GEMINI.md
## Turn-Stamp Rule
- 在每則回覆的最後一行印出:⏱ YYYY-MM-DD HH:MM
- 依據該輪 <ADDITIONAL_METADATA> 裡的本機時間計算
取捨:Antigravity CLI 這條路是用「指令依從」取代「機制保證」——hook 一定會觸發,Rule 有可能在長對話或 context compaction 後被模型忘記。三個工具,三種答案:Claude Code 的 Stop hook 直接給你一個顯示欄位;Codex 得靠 continue:false 這個非顯式技巧才能讓字串浮出來;Antigravity CLI 連 hook 這條路都走不通,得整個換成 prompt 層的解法。

不是唯一的嘗試:現有社群方案

專案做法
zoharbabin/claude-code-message-timestamps 25 MessageDisplay hook 前綴時間,README 引用 11 個相關 issue
s-a-s-k-i-a/claude-code-timestamps 6 讀 session log 取時間,拆成三個獨立開關(回顧指令、即時顯示、模型端注入)
clankercode/claude-inject-idle-time 4 state file 派:Stop 時明確算好 idle 時間寫入狀態檔再注入
evnchn-agentic/claude-code-time-awareness 4 掛在 PostToolUse 而非 UserPromptSubmit,鎖定無人值守的自動化 agent
VoxCore84/claude-code-timestamp-hook 0 架構上跟 prompt-timestamp 最像的無狀態單一 date shell-out,但沒有配套的人看端 plugin

多數競品選擇「不信任模型自己算時間差」,用 state file 明確算好塞進去;prompt-timestamp 反過來,讓時間戳留在對話歷史裡,讓模型自己算——零狀態、零依賴。「模型端/人端分開成兩個獨立 plugin」這個切法,目前只有 25★ 和 6★ 這兩個做得比較接近,但都沒做到這種程度的極簡。

收尾

楚門講了一輩子的時間問候語,卻是片場裡唯一搞不清楚真實時間的人。這兩個 plugin 沒有花俏的問候語,各自只有一行指令——但至少你的 AI 不會在大白天,對著剛開工的你說晚安。

GitHub →
← 返回作品集