File checkpointing theo dõi các thay đổi file được thực hiện qua tool Write, Edit, và NotebookEdit trong một agent session, cho phép bạn rewind (tua ngược) file về bất kỳ trạng thái nào trước đó.
Với checkpointing, bạn có thể:
- Undo thay đổi không mong muốn bằng cách khôi phục file về trạng thái tốt đã biết
- Khám phá phương án khác bằng cách khôi phục về một checkpoint rồi thử cách tiếp cận khác
- Khôi phục sau lỗi khi agent thực hiện thay đổi sai
Checkpointing hoạt động thế nào
Phần tiêu đề “Checkpointing hoạt động thế nào”Khi bạn bật file checkpointing, SDK tạo backup của file trước khi sửa đổi chúng qua tool Write, Edit, hoặc NotebookEdit. User message trong response stream có kèm một checkpoint UUID mà bạn có thể dùng làm điểm khôi phục.
Checkpoint hoạt động với các tool built-in sau mà agent dùng để sửa đổi file:
| Tool | Mô tả |
|---|---|
| Write | Tạo file mới hoặc ghi đè file có sẵn bằng nội dung mới |
| Edit | Thực hiện các sửa đổi có mục tiêu vào các phần cụ thể của file có sẵn |
| NotebookEdit | Sửa đổi cell trong Jupyter notebook (file .ipynb) |
Checkpoint system theo dõi:
- File được tạo trong session
- File được sửa đổi trong session
- Nội dung gốc của file đã sửa đổi
Khi bạn rewind về một checkpoint, Claude Code xoá các file nó đã tạo và khôi phục các file nó đã sửa về nội dung tại thời điểm đó. Claude Code bỏ qua một tracked path là symlink, hard link, hoặc file không phải regular file khác. Nó cũng bỏ qua một tracked file mà thư mục cha của nó không còn trỏ đến đúng vị trí tại thời điểm checkpoint, hoặc file mà nó không thể đọc backup an toàn. RewindFilesResult (TypeScript SDK reference) đếm mọi path bị bỏ qua trong trường skippedLinks. Việc bỏ qua này yêu cầu Claude Code v2.1.216 trở lên; trước v2.1.216, một lần rewind sẽ ghi và xoá xuyên qua link tại các tracked path.
Triển khai checkpointing
Phần tiêu đề “Triển khai checkpointing”Để dùng file checkpointing, bật nó trong option, lấy checkpoint UUID từ response stream, rồi gọi rewindFiles() (TypeScript) hoặc rewind_files() (Python) khi cần khôi phục.
Ví dụ sau cho thấy luồng đầy đủ: bật checkpointing, lấy checkpoint UUID và session ID từ response stream, sau đó resume session để rewind file. Mỗi bước được giải thích chi tiết bên dưới. Các ví dụ trong phần này dùng prompt “Refactor the authentication module”. Chạy chúng trong một project có chứa authentication module, hoặc đổi prompt để nhắc đến file có sẵn trong project của bạn, để bạn có thể quan sát file thay đổi và thấy rewind khôi phục chúng.
import asynciofrom claude_agent_sdk import ( ClaudeSDKClient, ClaudeAgentOptions, UserMessage, ResultMessage,)
async def main(): # Bước 1: Bật checkpointing options = ClaudeAgentOptions( enable_file_checkpointing=True, permission_mode="acceptEdits", # Tự động chấp nhận file edit mà không hỏi extra_args={ "replay-user-messages": None }, # Cần để nhận checkpoint UUID trong response stream )
checkpoint_id = None session_id = None
# Chạy query và lấy checkpoint UUID và session ID async with ClaudeSDKClient(options) as client: await client.query("Refactor the authentication module")
# Bước 2: Lấy checkpoint UUID từ user message đầu tiên async for message in client.receive_response(): if isinstance(message, UserMessage) and message.uuid and not checkpoint_id: checkpoint_id = message.uuid if isinstance(message, ResultMessage) and not session_id: session_id = message.session_id
# Bước 3: Sau đó, rewind bằng cách resume session với prompt rỗng if checkpoint_id and session_id: async with ClaudeSDKClient( ClaudeAgentOptions(enable_file_checkpointing=True, resume=session_id) ) as client: await client.query("") # Prompt rỗng để mở kết nối async for message in client.receive_response(): await client.rewind_files(checkpoint_id) break print(f"Rewound to checkpoint: {checkpoint_id}")
asyncio.run(main())import { query } from "@anthropic-ai/claude-agent-sdk";
async function main() { // Bước 1: Bật checkpointing const opts = { enableFileCheckpointing: true, permissionMode: "acceptEdits" as const, // Tự động chấp nhận file edit mà không hỏi extraArgs: { "replay-user-messages": null } // Cần để nhận checkpoint UUID trong response stream };
const response = query({ prompt: "Refactor the authentication module", options: opts });
let checkpointId: string | undefined; let sessionId: string | undefined;
// Bước 2: Lấy checkpoint UUID từ user message đầu tiên try { for await (const message of response) { if (message.type === "user" && message.uuid && !checkpointId) { checkpointId = message.uuid; } if ("session_id" in message && !sessionId) { sessionId = message.session_id; } } } catch (error) { // Một lời gọi query() single-shot throw lỗi sau khi yield result lỗi. Nếu // lỗi đến từ result lỗi, sessionId và checkpointId đã được lưu bởi vòng // lặp trên; lỗi kết nối hoặc process không yield result message nào cả. console.error(`Session ended with an error: ${error}`); }
// Bước 3: Sau đó, rewind bằng cách resume session với prompt rỗng if (checkpointId && sessionId) { const rewindQuery = query({ prompt: "", // Prompt rỗng để mở kết nối options: { ...opts, resume: sessionId } });
for await (const msg of rewindQuery) { await rewindQuery.rewindFiles(checkpointId); break; } console.log(`Rewound to checkpoint: ${checkpointId}`); }}
main();Bước 1: Bật checkpointing
Phần tiêu đề “Bước 1: Bật checkpointing”Cấu hình SDK option để bật checkpointing và nhận checkpoint UUID:
| Option | Python | TypeScript | Mô tả |
|---|---|---|---|
| Bật checkpointing | enable_file_checkpointing=True | enableFileCheckpointing: true | Theo dõi thay đổi file để rewind |
| Nhận checkpoint UUID | extra_args={"replay-user-messages": None} | extraArgs: { 'replay-user-messages': null } | Cần để lấy user message UUID trong stream |
options = ClaudeAgentOptions( enable_file_checkpointing=True, permission_mode="acceptEdits", extra_args={"replay-user-messages": None},)
async with ClaudeSDKClient(options) as client: await client.query("Refactor the authentication module")const response = query({ prompt: "Refactor the authentication module", options: { enableFileCheckpointing: true, permissionMode: "acceptEdits" as const, extraArgs: { "replay-user-messages": null } }});Bước 2: Lấy checkpoint UUID và session ID
Phần tiêu đề “Bước 2: Lấy checkpoint UUID và session ID”Với option replay-user-messages được đặt (như trên), mỗi user message trong response stream có kèm một UUID dùng làm checkpoint.
Với hầu hết use case, chỉ cần lấy UUID của user message đầu tiên (message.uuid); rewind về đó khôi phục các tracked file về trạng thái gốc của chúng. Để lưu nhiều checkpoint và rewind về các trạng thái trung gian, xem Nhiều điểm khôi phục.
Việc lấy session ID (message.session_id) là tùy chọn; bạn chỉ cần nó nếu muốn rewind sau, sau khi stream hoàn tất. Nếu bạn gọi rewindFiles() ngay trong lúc vẫn đang xử lý message (như ví dụ trong Checkpoint trước thao tác rủi ro), bạn có thể bỏ qua việc lấy session ID.
checkpoint_id = Nonesession_id = None
async for message in client.receive_response(): # Lấy UUID của user message đầu tiên làm checkpoint if isinstance(message, UserMessage) and message.uuid and checkpoint_id is None: checkpoint_id = message.uuid # Lấy session ID từ result message if isinstance(message, ResultMessage): session_id = message.session_idlet checkpointId: string | undefined;let sessionId: string | undefined;
for await (const message of response) { // Lấy UUID của user message đầu tiên làm checkpoint if (message.type === "user" && message.uuid && !checkpointId) { checkpointId = message.uuid; } // Lấy session ID từ bất kỳ message nào có trường này if ("session_id" in message) { sessionId = message.session_id; }}Bước 3: Rewind file
Phần tiêu đề “Bước 3: Rewind file”Để rewind sau khi stream hoàn tất, resume session với prompt rỗng và gọi rewind_files() (Python) hoặc rewindFiles() (TypeScript) với checkpoint UUID của bạn. Bạn cũng có thể rewind trong lúc stream đang chạy; xem Checkpoint trước thao tác rủi ro để biết mẫu đó.
async with ClaudeSDKClient( ClaudeAgentOptions(enable_file_checkpointing=True, resume=session_id)) as client: await client.query("") # Prompt rỗng để mở kết nối async for message in client.receive_response(): if checkpoint_id: await client.rewind_files(checkpoint_id) breakconst rewindQuery = query({ prompt: "", // Prompt rỗng để mở kết nối options: { ...opts, resume: sessionId }});
for await (const msg of rewindQuery) { if (checkpointId) { await rewindQuery.rewindFiles(checkpointId); } break;}Nếu bạn lưu cả session ID và checkpoint ID, bạn cũng có thể rewind từ CLI. Lệnh này cần executable claude, thứ đến từ việc cài đặt Claude Code và không được cài kèm SDK package. SDK tự bật checkpointing cho bạn, nhưng khi bạn chạy trực tiếp claude -p bạn phải đặt biến môi trường CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING:
CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING=true claude -p --resume <session-id> --rewind-files <checkpoint-uuid>Flag --rewind-files không xuất hiện trong output của claude --help, nhưng CLI vẫn chấp nhận nó như trên.
Các mẫu thường dùng
Phần tiêu đề “Các mẫu thường dùng”Các mẫu sau cho thấy những cách khác nhau để lấy và dùng checkpoint UUID tuỳ theo use case.
Checkpoint trước thao tác rủi ro
Phần tiêu đề “Checkpoint trước thao tác rủi ro”Mẫu này chỉ giữ checkpoint UUID gần nhất, cập nhật trước mỗi turn của agent. Nếu có gì đó sai trong lúc xử lý, bạn có thể rewind ngay về trạng thái an toàn cuối cùng và thoát khỏi vòng lặp.
Trước khi chạy ví dụ này, thay your_revert_condition (Python) hoặc yourRevertCondition (TypeScript) bằng điều kiện kiểm tra của riêng bạn, ví dụ như phát hiện lỗi hay validation thất bại; placeholder này không được định nghĩa trong ví dụ.
import asynciofrom claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, UserMessage
async def main(): options = ClaudeAgentOptions( enable_file_checkpointing=True, permission_mode="acceptEdits", extra_args={"replay-user-messages": None}, )
safe_checkpoint = None
async with ClaudeSDKClient(options) as client: await client.query("Refactor the authentication module")
async for message in client.receive_response(): # Cập nhật checkpoint trước khi mỗi turn của agent bắt đầu # Việc này ghi đè checkpoint trước. Chỉ giữ cái mới nhất if isinstance(message, UserMessage) and message.uuid: safe_checkpoint = message.uuid
# Quyết định khi nào revert dựa trên logic riêng của bạn # Ví dụ: phát hiện lỗi, validation thất bại, hoặc input người dùng if your_revert_condition and safe_checkpoint: await client.rewind_files(safe_checkpoint) # Thoát vòng lặp sau khi rewind, file đã được khôi phục break
asyncio.run(main())import { query } from "@anthropic-ai/claude-agent-sdk";
async function main() { const response = query({ prompt: "Refactor the authentication module", options: { enableFileCheckpointing: true, permissionMode: "acceptEdits" as const, extraArgs: { "replay-user-messages": null } } });
let safeCheckpoint: string | undefined;
for await (const message of response) { // Cập nhật checkpoint trước khi mỗi turn của agent bắt đầu // Việc này ghi đè checkpoint trước. Chỉ giữ cái mới nhất if (message.type === "user" && message.uuid) { safeCheckpoint = message.uuid; }
// Quyết định khi nào revert dựa trên logic riêng của bạn // Ví dụ: phát hiện lỗi, validation thất bại, hoặc input người dùng if (yourRevertCondition && safeCheckpoint) { await response.rewindFiles(safeCheckpoint); // Thoát vòng lặp sau khi rewind, file đã được khôi phục break; } }}
main();Nhiều điểm khôi phục
Phần tiêu đề “Nhiều điểm khôi phục”Nếu Claude thực hiện thay đổi qua nhiều turn, bạn có thể muốn rewind về một điểm cụ thể thay vì quay lại toàn bộ. Ví dụ, nếu Claude refactor một file ở turn một và thêm test ở turn hai, bạn có thể muốn giữ phần refactor nhưng undo phần test.
Mẫu này lưu tất cả checkpoint UUID trong một mảng kèm metadata. Sau khi session hoàn tất, bạn có thể rewind về bất kỳ checkpoint nào trước đó:
import asynciofrom dataclasses import dataclassfrom datetime import datetimefrom claude_agent_sdk import ( ClaudeSDKClient, ClaudeAgentOptions, UserMessage, ResultMessage,)
# Lưu metadata checkpoint để theo dõi tốt hơn@dataclassclass Checkpoint: id: str description: str timestamp: datetime
async def main(): options = ClaudeAgentOptions( enable_file_checkpointing=True, permission_mode="acceptEdits", extra_args={"replay-user-messages": None}, )
checkpoints = [] session_id = None
async with ClaudeSDKClient(options) as client: await client.query("Refactor the authentication module")
async for message in client.receive_response(): if isinstance(message, UserMessage) and message.uuid: checkpoints.append( Checkpoint( id=message.uuid, description=f"After turn {len(checkpoints) + 1}", timestamp=datetime.now(), ) ) if isinstance(message, ResultMessage) and not session_id: session_id = message.session_id
# Sau đó: rewind về bất kỳ checkpoint nào bằng cách resume session if checkpoints and session_id: target = checkpoints[0] # Chọn checkpoint bất kỳ async with ClaudeSDKClient( ClaudeAgentOptions(enable_file_checkpointing=True, resume=session_id) ) as client: await client.query("") # Prompt rỗng để mở kết nối async for message in client.receive_response(): await client.rewind_files(target.id) break print(f"Rewound to: {target.description}")
asyncio.run(main())import { query } from "@anthropic-ai/claude-agent-sdk";
// Lưu metadata checkpoint để theo dõi tốt hơninterface Checkpoint { id: string; description: string; timestamp: Date;}
async function main() { const opts = { enableFileCheckpointing: true, permissionMode: "acceptEdits" as const, extraArgs: { "replay-user-messages": null } };
const response = query({ prompt: "Refactor the authentication module", options: opts });
const checkpoints: Checkpoint[] = []; let sessionId: string | undefined;
try { for await (const message of response) { if (message.type === "user" && message.uuid) { checkpoints.push({ id: message.uuid, description: `After turn ${checkpoints.length + 1}`, timestamp: new Date() }); } if ("session_id" in message && !sessionId) { sessionId = message.session_id; } } } catch (error) { // Một lời gọi query() single-shot throw lỗi sau khi yield result lỗi. Nếu // lỗi đến từ result lỗi, sessionId và mảng checkpoints đã được lưu bởi // vòng lặp trên; lỗi kết nối hoặc process không yield result message nào cả. console.error(`Session ended with an error: ${error}`); }
// Sau đó: rewind về bất kỳ checkpoint nào bằng cách resume session if (checkpoints.length > 0 && sessionId) { const target = checkpoints[0]; // Chọn checkpoint bất kỳ const rewindQuery = query({ prompt: "", // Prompt rỗng để mở kết nối options: { ...opts, resume: sessionId } });
for await (const msg of rewindQuery) { await rewindQuery.rewindFiles(target.id); break; } console.log(`Rewound to: ${target.description}`); }}
main();Giới hạn
Phần tiêu đề “Giới hạn”File checkpointing có các giới hạn sau:
| Giới hạn | Mô tả |
|---|---|
| Chỉ tool Write/Edit/NotebookEdit | Thay đổi thực hiện qua lệnh Bash không được theo dõi |
| Edit của subagent | Edit mà một subagent áp dụng không được theo dõi hay khôi phục, ngoại trừ skill với context: fork chạy ở foreground; dùng git để revert các edit không được theo dõi |
| Cùng session | Checkpoint gắn với session đã tạo ra chúng |
| Chỉ nội dung file | Tạo, di chuyển, hoặc xoá thư mục không được undo khi rewind |
| File local | File từ xa hoặc trên mạng không được theo dõi |
Khắc phục sự cố
Phần tiêu đề “Khắc phục sự cố”Checkpointing option không được nhận diện
Phần tiêu đề “Checkpointing option không được nhận diện”Nếu enableFileCheckpointing hoặc rewindFiles() không khả dụng, có thể bạn đang dùng phiên bản SDK cũ.
Giải pháp: Cập nhật lên phiên bản SDK mới nhất:
- Python:
pip install --upgrade claude-agent-sdk - TypeScript:
npm install @anthropic-ai/claude-agent-sdk@latest
User message không có UUID
Phần tiêu đề “User message không có UUID”Nếu message.uuid là undefined hoặc thiếu, bạn không nhận được checkpoint UUID.
Nguyên nhân: Option replay-user-messages chưa được đặt.
Giải pháp: Thêm extra_args={"replay-user-messages": None} (Python) hoặc extraArgs: { 'replay-user-messages': null } (TypeScript) vào option của bạn.
Lỗi “No file checkpoint found for message”
Phần tiêu đề “Lỗi “No file checkpoint found for message””Lỗi này xảy ra khi dữ liệu checkpoint không tồn tại cho user message UUID được chỉ định.
Nguyên nhân thường gặp:
- File checkpointing không được bật trên session gốc (
enable_file_checkpointinghoặcenableFileCheckpointingkhông được đặt thànhtrue) - Session chưa hoàn tất đúng cách trước khi thử resume và rewind
Giải pháp: Đảm bảo enable_file_checkpointing=True (Python) hoặc enableFileCheckpointing: true (TypeScript) đã được đặt trên session gốc, sau đó dùng mẫu như trong các ví dụ: lấy UUID của user message đầu tiên, hoàn tất session đầy đủ, rồi resume với prompt rỗng và gọi rewindFiles() một lần.
Lỗi “File rewinding is not enabled”
Phần tiêu đề “Lỗi “File rewinding is not enabled””Lỗi này xảy ra khi bạn thử rewind non-interactive mà không bật checkpointing: chạy claude -p trần với --rewind-files, hoặc chạy một SDK session, kể cả session đã resume, mà option của nó không bật checkpointing. SDK chỉ tự đặt biến môi trường CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING nội bộ khi enable_file_checkpointing (Python) hoặc enableFileCheckpointing (TypeScript) được bật trên session thực hiện rewind; CLI trần không bao giờ tự đặt biến này.
Giải pháp: Với CLI trần, đặt biến môi trường khi chạy lệnh:
CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING=true claude -p --resume <session-id> --rewind-files <checkpoint-uuid>Với SDK, đặt enable_file_checkpointing=True (Python) hoặc enableFileCheckpointing: true (TypeScript) trên session đã resume, như các ví dụ trên trang này làm.
Lỗi “ProcessTransport is not ready for writing”
Phần tiêu đề “Lỗi “ProcessTransport is not ready for writing””Lỗi này xảy ra khi bạn gọi rewindFiles() hoặc rewind_files() sau khi đã lặp xong qua toàn bộ response. Kết nối đến CLI process đóng lại khi vòng lặp hoàn tất.
Giải pháp: Resume session với prompt rỗng, sau đó gọi rewind trên query mới:
# Resume session với prompt rỗng, sau đó rewindasync with ClaudeSDKClient( ClaudeAgentOptions(enable_file_checkpointing=True, resume=session_id)) as client: await client.query("") async for message in client.receive_response(): if checkpoint_id: await client.rewind_files(checkpoint_id) break// Resume session với prompt rỗng, sau đó rewindconst rewindQuery = query({ prompt: "", options: { ...opts, resume: sessionId }});
try { for await (const msg of rewindQuery) { if (checkpointId) { await rewindQuery.rewindFiles(checkpointId); } break; }} catch (error) { // Lỗi ở đây nghĩa là rewind không hoàn tất, ví dụ checkpoint không tìm // thấy hoặc session không resume được. console.error(`Rewind session ended with an error: ${error}`);}Bước tiếp theo
Phần tiêu đề “Bước tiếp theo”- Sessions: tìm hiểu cách resume session, điều cần thiết để rewind sau khi stream hoàn tất. Bao gồm session ID, resume hội thoại, và fork session.
- Permissions: cấu hình tool nào Claude được dùng và cách các thay đổi file được duyệt. Hữu ích nếu bạn muốn kiểm soát nhiều hơn khi nào edit xảy ra.
- TypeScript SDK reference: tham khảo API đầy đủ bao gồm mọi option của
query()và methodrewindFiles(). - Python SDK reference: tham khảo API đầy đủ bao gồm mọi option của
ClaudeAgentOptionsvà methodrewind_files().
lượt xem