"Good afternoon, good evening, and good night."
為什麼你的 AI 早上八點還在跟你說晚安
半夜十二點,你丟了句話給 Claude,沒收工,session 就晾在那。隔天早上八點你回來繼續,它劈頭就說:
不是它在等昨晚沒睡的你——是它壓根不知道現在是早上八點。它活在自己的片場裡,時間停在你上次離開的那一刻。
楚門的片場靠人工日夜燈光操控他對時間的認知,他毫無所覺地活在裡面,天天講著時間問候語,卻是全片場裡最搞不清楚真實時間的人。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、剩下的持續開著沒人處理。
方案一:prompt-timestamp(給 LLM 看)
一個 UserPromptSubmit hook,在每則訊息送出前注入:
%z 時區 offset 解決時區歧義,不需要任何設定。尾巴那句提示不是算好的值——它是靜態文字,引導模型自己回頭看對話歷史裡的前一個時間戳,自己算出間隔。
零 state file
不寫任何狀態到磁碟,context window 本身就是狀態。
零依賴
一行 date 指令,沒有額外腳本,沒有第三方套件。
零網路存取
完全本機執行,不打任何 API。
幾乎免費
每則訊息成本約 10 tokens。
方案二:turn-stamp(給人看)
prompt-timestamp 解決的是模型端;但「回頭考古對話記錄」這件事,是人的痛點,不是模型的痛點——這個需求是獨立冒出來的,不是看了誰的做法才想到。turn-stamp 用一個 Stop hook,在每輪結束時印出:
做到一半才發現,GitHub 上人氣最高的同類專案 zoharbabin/claude-code-message-timestamps(25★)已經做過同樣的事——概念上完全對,但實作比想要的重得多:
hooks.json + 4 個 shell script(含依賴檢查、時區解析)
單行 printf,寫在 hooks.json 裡,無額外腳本、無依賴檢查
安裝
不想裝 plugin,也可以直接把對應的 hook 貼進 ~/.claude/settings.json(README 裡有完整 JSON),但不要同時裝 plugin 又貼設定,會重複顯示。
跨平台差異:同一招,不一定通用
這段是整個過程裡最有意思的部分——直覺上同一個做法應該可以複製到其他 CLI 工具,實際上其中一個工具的底層架構直接把這招弄壞了。
Codex CLI:機制相同,輸出格式不同——而且真的有 turn-stamp 等價物
Codex CLI 也有 UserPromptSubmit,但它要求輸出結構化 JSON,不能像 Claude Code 一樣直接吃 plain stdout:
一開始以為 Codex 不需要 turn-stamp 的等價物——反正 hook 輸出本來就會印到終端機,已經看得到。後來發現這個假設是錯的:Codex 一般 hook 的輸出並不會直接顯示給人看,要讓一行字真的浮出來,得靠一個技巧——用 continue:false 搭配 stopReason 掛在 Stop 事件上,Codex 才會把 stopReason 的內容渲染成一行可見的 stop: 訊息:
兩個 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,讓模型自己在回覆最後一行印出時間戳:
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 不會在大白天,對著剛開工的你說晚安。