Bỏ qua để đến nội dung

Rewind file với checkpointing

Bài viết được dịch tự động từ bài viết gốc, chưa được kiểm tra lại bởi con người. Chỉ những bài viết có dấu tick xanh cạnh tiêu đề là đã được kiểm tra.

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

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:

ToolMô tả
WriteTạo file mới hoặc ghi đè file có sẵn bằng nội dung mới
EditThự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
NotebookEditSử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.

Để 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 asyncio
from 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();

Cấu hình SDK option để bật checkpointing và nhận checkpoint UUID:

OptionPythonTypeScriptMô tả
Bật checkpointingenable_file_checkpointing=TrueenableFileCheckpointing: trueTheo dõi thay đổi file để rewind
Nhận checkpoint UUIDextra_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 }
}
});

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 = None
session_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_id
let 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;
}
}

Để 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)
break
const 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:

Terminal window
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 sau cho thấy những cách khác nhau để lấy và dùng checkpoint UUID tuỳ theo use case.

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 asyncio
from 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();

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 asyncio
from dataclasses import dataclass
from datetime import datetime
from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions,
UserMessage,
ResultMessage,
)
# Lưu metadata checkpoint để theo dõi tốt hơn
@dataclass
class 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ơn
interface 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();

File checkpointing có các giới hạn sau:

Giới hạnMô tả
Chỉ tool Write/Edit/NotebookEditThay đổi thực hiện qua lệnh Bash không được theo dõi
Edit của subagentEdit 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 sessionCheckpoint gắn với session đã tạo ra chúng
Chỉ nội dung fileTạo, di chuyển, hoặc xoá thư mục không được undo khi rewind
File localFile từ xa hoặc trên mạng không được theo dõi

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

Nếu message.uuidundefined 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 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_checkpointing hoặc enableFileCheckpointing không được đặt thành true)
  • 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 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:

Terminal window
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 đó rewind
async 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 đó rewind
const 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}`);
}
  • 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à method rewindFiles().
  • Python SDK reference: tham khảo API đầy đủ bao gồm mọi option của ClaudeAgentOptions và method rewind_files().