AI 工程

Cline v4.1.7 拆解:checkpoint 其實存在你自己的 repo 裡,還有中斷時 prompt 佇列的兩種命運

你讓 Cline 跑一個大重構,跑到一半你發現方向錯了,按下 Escape。這時候問題來了:你在它工作期間順手打的那三則追加指令,還在不在?以及,這一輪它到底動了哪些檔案——你要怎麼在還沒 commit 之前一次看完?

Cline v4.1.7(2026-08-09 發佈,cline/cline)這一版把這兩件事同時處理掉了:把 completion row 上的 View Changes 按鈕接回 SDK checkpoint,以及讓中斷不再把排隊的 prompt 靜靜丟掉

這聽起來像兩個小修,但拆開看,底下是 Cline 從 3.x 到 4.x 整個 task 執行層換底盤之後,正在一塊一塊把功能接回來的過程。而且更值得寫的是:新的 checkpoint 機制跟官方文件寫的完全不是同一回事——文件說的是舊的 shadow git repo,實際跑的 SDK 程式碼是把快照存進你自己的 repo,用一個你看不到但 git 查得到的私有 ref 命名空間。這代表你其實可以自己下指令去驗、去比、去清。

以下都是從 v4.1.7 的 release notes、CHANGELOG 和 main 分支上的原始碼讀出來的(檔案路徑我會標),不是實測體感。

Cline SDK checkpoint 的建立流程:git stash create 加上 untracked 第三 parent,寫進 refs/cline/checkpoints 私有 ref

一、背景:4.0 換底盤,把一堆東西換掉了

先講清楚為什麼「Restore the View Changes button」會被寫成 Added——因為它本來就有,是在 4.0 弄丟的。

看 CHANGELOG 的時間線:

  • 4.0.0:Cline 團隊把 VS Code extension 從 legacy task 實作整個搬到共用的 Cline SDK。agent turn、工具、Plan/Act 協調、MCP、checkpoints、telemetry、compaction、mistake limit、task history 全部改走 SDK session layer。同一版還加了 queued prompts、edit-and-regenerate、Cline Plugins、Customize marketplace。順帶「Remove the legacy Explain Changes feature as part of the SDK migration cleanup」,subagents 也被暫時關掉。
  • 4.0.1:直接把 stable 版滾回遷移前的程式碼(把 3.89.2 的 extension 用更高版號重發,好讓已經升到 4.0.0 的人收到修正)。SDK 遷移改在 main 上繼續。
  • 4.1.0:改成 A/B 合併包——一個 VSIX 裡塞 legacy 和 SDK 兩份 extension,加一個 loader 每個視窗只啟用一份,用遠端分階段放量,從 1% 開始。新的啟動失敗就在同一個視窗 fallback 回舊的,設定和憑證兩邊共用。
  • 4.1.3:「Restore reliable checkpoints」——checkpoint 建立變穩定,restore 也從只還原部分檔案修成整個 workspace。
  • 4.1.7:View Changes 接回 SDK checkpoint;untracked 檔案進 diff;session 中途 git init 也能被撿到。

所以 4.1.7 不是「新功能」,是新底盤補上舊功能的最後一塊拼圖。如果你這幾個月覺得 Cline 的 checkpoint 有點飄,那不是錯覺,是你被放量到 SDK 版了。

順帶一提,這個 A/B 包也解釋了 4.1.2 為什麼要在設定的 About 頁面標「Legacy」或「Next」——這是你第一件該做的事:打開 Cline 設定 → About,看你這個視窗到底跑的是哪一份。底下講的所有 checkpoint 行為,只在 Next(SDK)版成立。

二、運作原理:checkpoint 不在 shadow repo,就在你的 repo 裡

官方文件頁 docs.cline.bot/features/checkpoints(以及 repo 裡的 docs/core-workflows/checkpoints.mdx)到現在寫的還是舊模型:

Cline maintains a shadow Git repository separate from your project's actual Git history. After each tool use (file edits, commands, etc.), Cline commits the current state of your files to this shadow repo.

但 SDK 的實作 sdk/packages/core/src/hooks/checkpoint-hooks.ts 做的是另一回事。拆成三段講。

1. 觸發時機:一個 user turn 一個 checkpoint,不是一個 tool use 一個

hook 掛在 beforeModel,而且開頭就擋掉兩種情況:snapshot.parentAgentId != null(subagent 不建 checkpoint)和 snapshot.iteration !== 1(只在該 run 的第一次呼叫模型時建)。編號用的是 countUserRunMessages(snapshot.messages),也就是你送出的訊息數

再看 apps/vscode/src/sdk/sdk-checkpoints.ts 怎麼定義「一個 user run」:isCheckpointRunUserMessage() 會把「你回答 Cline 提問(followup / mistake_limit_reached)」的那則訊息排除掉。也就是說,你回答 agent 的追問不會多開一個 checkpoint,只有你主動起頭的那一輪才會。

這件事直接改變你該怎麼下指令:checkpoint 的顆粒度=你送出訊息的顆粒度。一則訊息塞三件不相干的事,你就只會拿到一個包含三件事的 snapshot,回不到中間。想要細一點的回溯點,就把任務拆成多則訊息送。

2. 快照怎麼做:git stash create + 一個合成的第三 parent

核心函式是 createWorktreeStashCommit()

  1. 先跑 git stash create <message>。這個指令會做出一個 stash 形狀的 commit 物件(兩個 parent:HEAD 和 index),但不動你的工作目錄、不寫 refs/stash
  2. git stash create 天生不含 untracked 檔案,所以 Cline 自己補一個:用 git ls-files --others --exclude-standard -z 列出 untracked 檔案,開一個 temp 目錄當 GIT_INDEX_FILEgit add --force --pathspec-from-file,然後 write-tree + commit-tree 做出一顆只含 untracked 檔案的 commit。
  3. commit-tree 把原本的 tree 接上三個 parent(base、index、untracked),做出一個 git stash apply 認得的三 parent commit。
  4. 最後 git update-ref refs/cline/checkpoints/{sessionId}/{runCount} <sha>

第 4 步是設計上最漂亮的一手,程式碼註解講得很直接:refs/stash 才是 git stash list 讀的地方,寫到別的 ref 路徑,物件既不會被 GC 掉,也不會污染你的 stash 列表

還有兩個 fallback 值得記:

  • 工作區乾淨、也沒有 untracked 檔案 → 退回 createHeadCheckpoint(),直接記 HEAD 的 sha,kind: "commit"
  • 整段 snapshot 失敗(例外)→ 一樣退回 HEAD checkpoint,並寫一則 warn log。

注意 --exclude-standard:這個 flag 會排除 .gitignore 命中的檔案。所以文件那句「Checkpoints capture everything, including files not tracked by Git」,對新的 SDK 路徑只對一半——untracked 有進去,gitignored 沒有。你的 .envnode_modules、build 產物不在 snapshot 裡。這點很重要,下面會再講一次。

3. 你可以自己查:五條指令

這是本文最實用的部分。因為 checkpoint 就是你 repo 裡的 git 物件,你不需要透過 Cline UI 就能查:

# 1. 列出這個 repo 裡所有 Cline checkpoint(會看到 session id 和 run 編號)
git for-each-ref --format='%(refname) %(objectname:short)' 'refs/cline/checkpoints/**'

# 2. 看某個 checkpoint 相對它的 base 動了什麼
git show --stat refs/cline/checkpoints/<sessionId>/3

# 3. 從那個 checkpoint 到「現在的工作目錄」動了哪些檔案
#    —— 這就是 View Changes 按鈕在算的東西
git diff --name-only refs/cline/checkpoints/<sessionId>/3

# 4. 看那個 checkpoint 當下有哪些 untracked 檔案(第三 parent)
git ls-tree -r --name-only refs/cline/checkpoints/<sessionId>/3^3

# 5. 手動把某個 checkpoint 的狀態疊回來(不經過 Cline 的 restore)
git stash apply refs/cline/checkpoints/<sessionId>/3

第 5 條之所以能動,是因為 createWorktreeStashCommit 刻意把 commit 做成 stash 形狀——原始碼註解寫明「The raw SHA already works with git stash apply on the restore path, so no restore-side changes are needed」。

清理(大 repo 用久了這些 ref 會累積):

git for-each-ref --format='%(refname)' 'refs/cline/checkpoints/**' \
  | xargs -n1 git update-ref -d

SDK 裡對應的是 deleteCheckpointRefs()retainCheckpointRefs(),做的就是同一件事。

三、View Changes 到底 diff 什麼——以及一個要注意的落差

apps/vscode/src/core/controller/checkpoints/checkpointViewLatestChanges.ts 的註解寫得很精確:

Opens a multi-file diff of everything that changed between the latest checkpoint (snapshotted when the user's last message started a run) and the current working tree.

而 release notes 的說法是「review everything a task touched from the completion card」。這兩句不完全一樣:程式碼講的是「最後一則 user 訊息開始那一刻」到現在,不是整個 task 從頭到尾。單輪的 task 兩者等價;多輪來回的 task,你在完成卡上看到的是最後一輪的增量。

我沒有實際跑過 v4.1.7 去驗這個落差在 UI 上長怎樣,這裡只能標出「文件敘述與程式碼註解不一致,以程式碼為準的可能性較高」。要自己驗很簡單:對照上面第 3 條 git diff --name-only 的檔案清單跟按鈕開出來的 diff,看數量對不對得上。

至於「Fade the View Changes button until changes since the last message are confirmed,and hide it entirely when there is nothing to show」,實作在 checkpointLatestChangesCount.ts:它回傳最新 checkpoint 到工作目錄之間變更的檔案數,拿不到就回 0,webview 拿這個數字決定按鈕要不要亮。所以按鈕灰著不代表壞掉,代表「還在算」或「真的沒東西」。

而 4.1.7 那條「Include files that were untracked when a snapshot was taken in checkpoint diffs」,對應的是 sdk/packages/core/src/session/checkpoint-diff.ts 裡的 readCheckpointFile():先試 git show <ref>:<path>,失敗就 fallback 到 git show <ref>^3:<path>。修掉的 bug 很具體——快照當下就已經存在的 untracked 檔案,之前會被誤判成「新增檔案」,因為它們不在 stash commit 自己的 tree 裡,而在第三 parent。

另一條「pick up checkpoints when git is initialized part-way through a session」則在 ensureGitRepository():它只快取「是 git repo」這個肯定答案,否定的答案每一輪重驗。註解講明理由——你可能在 session 中途才 git init,這個探測每個 user turn 只跑一次,重驗很便宜。

四、中斷之後,排隊的 prompt 會怎樣

第二條主線在 sdk/packages/core/src/runtime/turn-queue/pending-prompt-service.ts。這裡有兩個你一定要分清楚的概念。

中斷 Cline 時排隊 prompt 的兩種結局,以及 queue 與 steer 兩種投遞方式的差別

概念一:queue vs steer,是兩種投遞方式

型別定義是 type PendingPromptDelivery = "queue" | "steer"

  • queuestate.pendingPrompts.push(entry) — 排到隊尾,等當前 turn 結束才輪到。
  • steerstate.pendingPrompts.unshift(entry) — 插到隊首,而且由 consumeSteer() 專門撈出來餵進正在跑的那一輪

UI 上分得出來:QueuedPrompts.tsx 會在 steer 的項目旁邊掛一個 Steer 標籤,摘要列也會寫成像「2 queued, 1 steering」。CLI 那邊 use-queued-prompts.tssteer: entry.delivery === "steer" 做同樣的區分。

實務上的用法差別很清楚:「你走錯方向了,改用 X 做法」是 steer,「做完這個順便幫我補測試」是 queue。前者要立刻進到當前 turn 的脈絡,後者不該打斷正在跑的東西。

概念二:中斷的兩種結局,取決於「你中斷的是誰送出的 turn」

這是 4.1.7 修的核心,discardQueue() 上面的註解寫得毫不含糊:

Drops every queued prompt. Only called when the user aborts a queue-initiated turn — that gesture means "stop the queued work", not just "stop this response", so the remainder must not auto-run.

翻成人話:

  • 你自己送出的 turn 被你中斷 → 佇列活著。而且 enqueue() 的註解說明,abort 正在收尾的那段時間裡佇列操作仍然可用——你按完 Escape 馬上打的字會加進佇列而不是被吞掉,已排隊的項目也還能編輯、刪除。
  • 佇列自己送出的 turn 被你中斷discardQueue()剩下全部清掉。因為這個手勢的意思是「停止整批排隊的工作」。

這條規則不知道就會踩雷:你排了五則訊息去睡覺,早上起來看到第二則跑歪,順手 Escape——後面三則全沒了,而且是設計如此。想只停一則、保留其他,正確做法是用 QueuedPrompts 面板上每一列的 × 逐則取消(走 cancelQueuedPrompt),不要按停止。

排空邏輯的兩個防呆

drain() 還有兩個細節值得知道:

  • turn 回傳 error finish(例如 provider 掛了)→ 不 requeue(因為那則 prompt 已經進到對話裡、錯誤也顯示了),但停止繼續排空。註解的理由是「不要把剩下的佇列一路灌進一個正在失敗的 provider」。剩下的留在佇列裡,等下次 enqueue、編輯或一次成功的 turn 再繼續。
  • send() 直接拋例外requeueFront() 把它塞回隊首,不丟。

另外 4.1.7 還修了「Report queued-turn failures as run.failed instead of letting them complete silently」——在這之前,佇列跑出來的 turn 失敗了可能不會有明顯訊號。如果你有在用排隊跑長批次,這條比 View Changes 更該讓你升級。

五、數字與限制:誠實講,這版沒有 benchmark

這是一個 bug-fix 導向的 patch release,release notes 裡沒有任何效能或準確率數字。能引用的具體數字都是程式碼常數和 rollout 參數,我把查得到的列出來:

數字 出處 意思
30 秒 v4.1.7 release notes 沒被設定過的 stdio MCP server 的 initialize 預算,超過就放棄,不再無限期擋住 session 建立
1% v4.1.0 CHANGELOG SDK 版 extension 遠端放量的起始比例
50 MB checkpoint-diff.tsMAX_GIT_OUTPUT 單次 git 輸出上限
64 MB checkpoint-hooks.tsmaxBuffer 列 untracked 檔案時的 buffer 上限
5 分鐘 v4.1.3 CHANGELOG Ollama 的 response-start timeout,為冷啟動模型加大

限制的部分,講四個真的會咬到人的:

1. 沒有 git repo 就沒有 checkpoint。 ensureGitRepository() 回 false 就直接不建。4.1.7 之後你中途 git init 會被撿到,但在那之前跑的那幾輪,補不回來。

2. gitignored 檔案不在快照裡。 --exclude-standard 決定的。.env、遷移產出、本地 fixture、build 目錄——restore 不會幫你還原,也不會幫你救回。這跟官方文件那句「capture everything」是衝突的,以程式碼為準

3. restore 對工作目錄是破壞性的。 checkpoint-restore.tsbeginWorktreeRestoreTransaction() 註解寫明 restore 路徑會跑 git clean -fd,所以它會先用 git stash push --include-untracked 存一份保險,再把物件搬到 refs/cline/restore-transactions/{uuid} 私有 ref 底下、並立刻從你可見的 stash 列表 drop 掉,直到 commit 或 rollback 才清。有安全網,但別把它當成「反正可以還原」的免死金牌——git clean -fd 不帶 -x,所以 gitignored 檔案不會被刪,但也就更加不在保護範圍內。

4. 大 repo 有成本。 每個 user turn 都在做 stash create + 遍歷 untracked 檔案 + 寫 ref。官方文件對舊機制的警告(「for very large repositories, checkpoints may use significant storage and slow down Cline」)在新機制下方向仍然成立。設定路徑:Cline 設定 → Feature Settings → Enable Checkpoints。

六、什麼時候該用、什麼時候別用

適合開著 checkpoint 的情境:auto-approve 開著跑大範圍重構、跑不熟悉的 codebase、讓 agent 連續動十幾個檔案。checkpoint 的價值就是把「犯錯成本」壓到接近零,這樣你才敢不逐條 review。

該關掉的情境:monorepo 或超大 repo 而你感覺得到每輪開頭有停頓;或者你本來就習慣一個小任務一個 git commit,那 checkpoint 對你是重複的保險。

別依賴 checkpoint 的情境:任何涉及 gitignored 檔案的操作。改 .env、跑 migration、動 build 產物——這些自己另外備份。

七、給工程團隊的操作清單

  1. 先確認你在哪一份 extension 上。 設定 → About,看是 Legacy 還是 Next。是 Legacy 的話,本文講的 checkpoint 機制對你不成立。
  2. 升到 4.1.7。 尤其如果你有在用 queued prompts 跑批次——run.failed 那條修正讓失敗不再靜默。
  3. 把「一則訊息一件事」寫進團隊慣例。 現在這條有機械理由:checkpoint 顆粒度 = user turn 顆粒度。
  4. 教會團隊 Escape 的兩種語意。 中斷自己的 turn ≠ 中斷佇列的 turn。要精準取消請用 × 逐則刪,不要按停止。
  5. 把 steer 跟 queue 用對。 改方向用 steer,加工作用 queue。
  6. 把那五條 git 指令收進 runbook。 「Cline 這輪到底改了什麼」不需要等 UI,git diff --name-only refs/cline/checkpoints/<sessionId>/<n> 直接查。
  7. 加一條 ref 清理。 大 repo 定期 git for-each-ref ... | xargs -n1 git update-ref -d,或在 session 結束時讓 Cline 自己清(deleteCheckpointRefs)。
  8. 在 onboarding 文件裡標註官方 checkpoint 文件已過時。 新人照著 shadow repo 的說法去找檔案會找不到。

最後一句判斷:v4.1.7 本身是小版本,但它標示的是 Cline SDK 遷移終於把 checkpoint 這條線收乾淨。如果你在 4.0.x 到 4.1.2 之間被 checkpoint 的不穩定勸退過,這是可以回來再試一次的時間點。

來源

整理:DataAgent · Coding Agent 實戰教學

發表迴響

%d 位部落客按了讚: