برگرداندنِ تغییراتِ فایل با checkpointing
File checkpointing تغییراتِ فایل را که از طریقِ ابزارهای Write، Edit و NotebookEdit در طولِ یک نشستِ ایجنت انجام شدهاند ردگیری میکند، و به تو اجازه میدهد فایلها را به هر حالتِ پیشین برگردانی. میخواهی امتحانش کنی؟ به مثالِ تعاملی برو.
با checkpointing میتوانی:
- تغییراتِ ناخواسته را خنثی کنی با برگرداندنِ فایلها به یک حالتِ سالمِ شناختهشده
- جایگزینها را کاوش کنی با برگشت به یک checkpoint و امتحانِ رویکردی متفاوت
- از خطاها بازیابی کنی وقتی ایجنت تغییراتِ نادرست اعمال میکند
checkpointing چطور کار میکند
Section titled “checkpointing چطور کار میکند”وقتی file checkpointing را فعال میکنی، SDK پیش از تغییردادنِ فایلها از طریقِ ابزارهای Write، Edit یا NotebookEdit از آنها backup میگیرد. پیامهای user در جریانِ پاسخ یک checkpoint UUID دارند که میتوانی بهعنوانِ نقطهی بازگشت استفاده کنی.
Checkpoint با این ابزارهای توکار که ایجنت برای تغییرِ فایلها به کار میبرد کار میکند:
| ابزار | توضیح |
|---|---|
| Write | یک فایلِ جدید میسازد یا فایلِ موجود را با محتوای جدید بازنویسی میکند |
| Edit | ویرایشهای هدفمند روی بخشهای مشخصی از یک فایلِ موجود انجام میدهد |
| NotebookEdit | سلولها را در نوتبوکهای Jupyter (فایلهای .ipynb) تغییر میدهد |
سیستمِ checkpoint اینها را ردگیری میکند:
- فایلهای ساختهشده در طولِ نشست
- فایلهای تغییریافته در طولِ نشست
- محتوای اصلیِ فایلهای تغییریافته
وقتی به یک checkpoint برمیگردی، فایلهای ساختهشده حذف میشوند و فایلهای تغییریافته به محتوایشان در آن نقطه برگردانده میشوند.
checkpointing را پیادهسازی کن
Section titled “checkpointing را پیادهسازی کن”برای استفاده از file checkpointing، آن را در options فعال کن، checkpoint UUIDها را از جریانِ پاسخ بگیر، سپس وقتی نیاز به بازگرداندن داشتی rewindFiles() (TypeScript) یا rewind_files() (Python) را فراخوانی کن.
مثالِ زیر جریانِ کامل را نشان میدهد: checkpointing را فعال کن، checkpoint UUID و session ID را از جریانِ پاسخ بگیر، سپس بعداً نشست را resume کن تا فایلها را برگردانی. هر گام در پایین بهتفصیل توضیح داده شده.
import asynciofrom claude_agent_sdk import ( ClaudeSDKClient, ClaudeAgentOptions, UserMessage, ResultMessage,)
async def main(): # Step 1: Enable checkpointing options = ClaudeAgentOptions( enable_file_checkpointing=True, permission_mode="acceptEdits", # Auto-accept file edits without prompting extra_args={ "replay-user-messages": None }, # Required to receive checkpoint UUIDs in the response stream )
checkpoint_id = None session_id = None
# Run the query and capture checkpoint UUID and session ID async with ClaudeSDKClient(options) as client: await client.query("Refactor the authentication module")
# Step 2: Capture checkpoint UUID from the first user message 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
# Step 3: Later, rewind by resuming the session with an empty prompt if checkpoint_id and session_id: async with ClaudeSDKClient( ClaudeAgentOptions(enable_file_checkpointing=True, resume=session_id) ) as client: await client.query("") # Empty prompt to open the connection 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() { // Step 1: Enable checkpointing const opts = { enableFileCheckpointing: true, permissionMode: "acceptEdits" as const, // Auto-accept file edits without prompting extraArgs: { "replay-user-messages": null } // Required to receive checkpoint UUIDs in the response stream };
const response = query({ prompt: "Refactor the authentication module", options: opts });
let checkpointId: string | undefined; let sessionId: string | undefined;
// Step 2: Capture checkpoint UUID from the first user message 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; } }
// Step 3: Later, rewind by resuming the session with an empty prompt if (checkpointId && sessionId) { const rewindQuery = query({ prompt: "", // Empty prompt to open the connection options: { ...opts, resume: sessionId } });
for await (const msg of rewindQuery) { await rewindQuery.rewindFiles(checkpointId); break; } console.log(`Rewound to checkpoint: ${checkpointId}`); }}
main();checkpointing را فعال کن
optionsهای SDK را پیکربندی کن تا checkpointing را فعال کنی و checkpoint UUIDها را دریافت کنی:
| گزینه | Python | TypeScript | توضیح |
|---|---|---|---|
| فعالکردنِ checkpointing | enable_file_checkpointing=True | enableFileCheckpointing: true | تغییراتِ فایل را برای برگرداندن ردگیری میکند |
| دریافتِ checkpoint UUIDها | extra_args={"replay-user-messages": None} | extraArgs: { 'replay-user-messages': null } | برای گرفتنِ UUIDهای پیامِ user در جریان الزامی است |
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 } }});checkpoint UUID و session ID را بگیر
با تنظیمِ گزینهی replay-user-messages (که در بالا نشان داده شد)، هر پیامِ user در جریانِ پاسخ یک UUID دارد که بهعنوانِ یک checkpoint عمل میکند.
برای بیشترِ کاربردها، UUIDِ اولین پیامِ user (message.uuid) را بگیر؛ برگشت به آن همهی فایلها را به حالتِ اصلیشان برمیگرداند. برای ذخیرهی چند checkpoint و برگشت به حالتهای میانی، به چند نقطهی بازگشت نگاه کن.
گرفتنِ session ID (message.session_id) اختیاری است؛ فقط وقتی به آن نیاز داری که بخواهی بعداً، پس از کاملشدنِ جریان، برگردانی. اگر rewindFiles() را بلافاصله و هنوز در حالِ پردازشِ پیامها فراخوانی میکنی (همانطور که مثالِ checkpoint پیش از عملیاتِ پرخطر میکند)، میتوانی از گرفتنِ session ID صرفِنظر کنی.
checkpoint_id = Nonesession_id = None
async for message in client.receive_response(): # Update checkpoint on each user message (keeps the latest) if isinstance(message, UserMessage) and message.uuid: checkpoint_id = message.uuid # Capture session ID from the result message if isinstance(message, ResultMessage): session_id = message.session_idlet checkpointId: string | undefined;let sessionId: string | undefined;
for await (const message of response) { // Update checkpoint on each user message (keeps the latest) if (message.type === "user" && message.uuid) { checkpointId = message.uuid; } // Capture session ID from any message that has it if ("session_id" in message) { sessionId = message.session_id; }}فایلها را برگردان
برای برگرداندن پس از کاملشدنِ جریان، نشست را با یک پرامپتِ خالی resume کن و rewind_files() (Python) یا rewindFiles() (TypeScript) را با checkpoint UUIDات فراخوانی کن. میتوانی در طولِ جریان هم برگردانی؛ برای آن الگو به checkpoint پیش از عملیاتِ پرخطر نگاه کن.
async with ClaudeSDKClient( ClaudeAgentOptions(enable_file_checkpointing=True, resume=session_id)) as client: await client.query("") # Empty prompt to open the connection async for message in client.receive_response(): await client.rewind_files(checkpoint_id) breakconst rewindQuery = query({ prompt: "", // Empty prompt to open the connection options: { ...opts, resume: sessionId }});
for await (const msg of rewindQuery) { await rewindQuery.rewindFiles(checkpointId); break;}اگر session ID و checkpoint ID را بگیری، میتوانی از CLI هم برگردانی:
claude -p --resume <session-id> --rewind-files <checkpoint-uuid>الگوهای رایج
Section titled “الگوهای رایج”این الگوها روشهای مختلفِ گرفتن و استفاده از checkpoint UUIDها را بسته به کاربردت نشان میدهند.
checkpoint پیش از عملیاتِ پرخطر
Section titled “checkpoint پیش از عملیاتِ پرخطر”این الگو فقط جدیدترین checkpoint UUID را نگه میدارد و آن را پیش از هر نوبتِ ایجنت بهروزرسانی میکند. اگر در طولِ پردازش چیزی اشتباه شد، میتوانی بلافاصله به آخرین حالتِ امن برگردی و از حلقه بیرون بزنی.
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(): # Update checkpoint before each agent turn starts # This overwrites the previous checkpoint. Only keep the latest if isinstance(message, UserMessage) and message.uuid: safe_checkpoint = message.uuid
# Decide when to revert based on your own logic # For example: error detection, validation failure, or user input if your_revert_condition and safe_checkpoint: await client.rewind_files(safe_checkpoint) # Exit the loop after rewinding, files are restored 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) { // Update checkpoint before each agent turn starts // This overwrites the previous checkpoint. Only keep the latest if (message.type === "user" && message.uuid) { safeCheckpoint = message.uuid; }
// Decide when to revert based on your own logic // For example: error detection, validation failure, or user input if (yourRevertCondition && safeCheckpoint) { await response.rewindFiles(safeCheckpoint); // Exit the loop after rewinding, files are restored break; } }}
main();چند نقطهی بازگشت
Section titled “چند نقطهی بازگشت”اگر Claude در چند نوبت تغییراتی اعمال کند، شاید بخواهی به یک نقطهی مشخص برگردی نه تا انتها. مثلاً اگر Claude در نوبتِ اول یک فایل را بازآرایی کند و در نوبتِ دوم تست اضافه کند، شاید بخواهی بازآرایی را نگه داری ولی تستها را خنثی کنی.
این الگو همهی checkpoint UUIDها را در یک آرایه همراه با متادیتا ذخیره میکند. پس از کاملشدنِ نشست، میتوانی به هر checkpointِ پیشین برگردی:
import asynciofrom dataclasses import dataclassfrom datetime import datetimefrom claude_agent_sdk import ( ClaudeSDKClient, ClaudeAgentOptions, UserMessage, ResultMessage,)
# Store checkpoint metadata for better tracking@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
# Later: rewind to any checkpoint by resuming the session if checkpoints and session_id: target = checkpoints[0] # Pick any checkpoint async with ClaudeSDKClient( ClaudeAgentOptions(enable_file_checkpointing=True, resume=session_id) ) as client: await client.query("") # Empty prompt to open the connection 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";
// Store checkpoint metadata for better trackinginterface 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;
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; } }
// Later: rewind to any checkpoint by resuming the session if (checkpoints.length > 0 && sessionId) { const target = checkpoints[0]; // Pick any checkpoint const rewindQuery = query({ prompt: "", // Empty prompt to open the connection options: { ...opts, resume: sessionId } });
for await (const msg of rewindQuery) { await rewindQuery.rewindFiles(target.id); break; } console.log(`Rewound to: ${target.description}`); }}
main();امتحانش کن
Section titled “امتحانش کن”این مثالِ کامل یک فایلِ کمکیِ کوچک میسازد، میگذارد ایجنت به آن کامنتهای مستندسازی اضافه کند، تغییرات را نشانت میدهد، سپس میپرسد آیا میخواهی برگردانی.
پیش از شروع، مطمئن شو که Claude Agent SDK نصب شده داری.
یک فایلِ تست بساز
یک فایلِ جدید به نامِ utils.py (Python) یا utils.ts (TypeScript) بساز و کدِ زیر را در آن بچسبان:
def add(a, b): return a + b
def subtract(a, b): return a - b
def multiply(a, b): return a * b
def divide(a, b): if b == 0: raise ValueError("Cannot divide by zero") return a / bexport function add(a: number, b: number): number { return a + b;}
export function subtract(a: number, b: number): number { return a - b;}
export function multiply(a: number, b: number): number { return a * b;}
export function divide(a: number, b: number): number { if (b === 0) { throw new Error("Cannot divide by zero"); } return a / b;}مثالِ تعاملی را اجرا کن
یک فایلِ جدید به نامِ try_checkpointing.py (Python) یا try_checkpointing.ts (TypeScript) در همان دایرکتوریِ فایلِ کمکیات بساز و کدِ زیر را در آن بچسبان.
این اسکریپت از Claude میخواهد به فایلِ کمکیات کامنتهای مستندسازی اضافه کند، سپس به تو این گزینه را میدهد که برگردانی و نسخهی اصلی را بازیابی کنی.
import asynciofrom claude_agent_sdk import ( ClaudeSDKClient, ClaudeAgentOptions, UserMessage, ResultMessage,)
async def main(): # Configure the SDK with checkpointing enabled # - enable_file_checkpointing: Track file changes for rewinding # - permission_mode: Auto-accept file edits without prompting # - extra_args: Required to receive user message UUIDs in the stream options = ClaudeAgentOptions( enable_file_checkpointing=True, permission_mode="acceptEdits", extra_args={"replay-user-messages": None}, )
checkpoint_id = None # Store the user message UUID for rewinding session_id = None # Store the session ID for resuming
print("Running agent to add doc comments to utils.py...\n")
# Run the agent and capture checkpoint data from the response stream async with ClaudeSDKClient(options) as client: await client.query("Add doc comments to utils.py")
async for message in client.receive_response(): # Capture the first user message UUID - this is our restore point if isinstance(message, UserMessage) and message.uuid and not checkpoint_id: checkpoint_id = message.uuid # Capture the session ID so we can resume later if isinstance(message, ResultMessage): session_id = message.session_id
print("Done! Open utils.py to see the added doc comments.\n")
# Ask the user if they want to rewind the changes if checkpoint_id and session_id: response = input("Rewind to remove the doc comments? (y/n): ")
if response.lower() == "y": # Resume the session with an empty prompt, then rewind async with ClaudeSDKClient( ClaudeAgentOptions(enable_file_checkpointing=True, resume=session_id) ) as client: await client.query("") # Empty prompt opens the connection async for message in client.receive_response(): await client.rewind_files(checkpoint_id) # Restore files break
print( "\n✓ File restored! Open utils.py to verify the doc comments are gone." ) else: print("\nKept the modified file.")
asyncio.run(main())import { query } from "@anthropic-ai/claude-agent-sdk";import * as readline from "readline";
async function main() { // Configure the SDK with checkpointing enabled // - enableFileCheckpointing: Track file changes for rewinding // - permissionMode: Auto-accept file edits without prompting // - extraArgs: Required to receive user message UUIDs in the stream const opts = { enableFileCheckpointing: true, permissionMode: "acceptEdits" as const, extraArgs: { "replay-user-messages": null } };
let sessionId: string | undefined; // Store the session ID for resuming let checkpointId: string | undefined; // Store the user message UUID for rewinding
console.log("Running agent to add doc comments to utils.ts...\n");
// Run the agent and capture checkpoint data from the response stream const response = query({ prompt: "Add doc comments to utils.ts", options: opts });
for await (const message of response) { // Capture the first user message UUID - this is our restore point if (message.type === "user" && message.uuid && !checkpointId) { checkpointId = message.uuid; } // Capture the session ID so we can resume later if ("session_id" in message) { sessionId = message.session_id; } }
console.log("Done! Open utils.ts to see the added doc comments.\n");
// Ask the user if they want to rewind the changes if (checkpointId && sessionId) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const answer = await new Promise<string>((resolve) => { rl.question("Rewind to remove the doc comments? (y/n): ", resolve); }); rl.close();
if (answer.toLowerCase() === "y") { // Resume the session with an empty prompt, then rewind const rewindQuery = query({ prompt: "", // Empty prompt opens the connection options: { ...opts, resume: sessionId } });
for await (const msg of rewindQuery) { await rewindQuery.rewindFiles(checkpointId); // Restore files break; }
console.log("\n✓ File restored! Open utils.ts to verify the doc comments are gone."); } else { console.log("\nKept the modified file."); } }}
main();این مثال ورکفلوی کاملِ checkpointing را نشان میدهد:
- فعالکردنِ checkpointing: SDK را با
enable_file_checkpointing=Trueوpermission_mode="acceptEdits"پیکربندی کن تا ویرایشهای فایل خودکار تأیید شوند - گرفتنِ دادهی checkpoint: همانطور که ایجنت اجرا میشود، UUIDِ اولین پیامِ user (نقطهی بازگشتت) و session ID را ذخیره کن
- درخواست برای برگرداندن: پس از پایانِ کارِ ایجنت، فایلِ کمکیات را بررسی کن تا کامنتهای مستندسازی را ببینی، سپس تصمیم بگیر آیا میخواهی تغییرات را خنثی کنی
- resume و برگرداندن: اگر بله، نشست را با یک پرامپتِ خالی resume کن و
rewind_files()را فراخوانی کن تا فایلِ اصلی بازیابی شود
مثال را اجرا کن
اسکریپت را از همان دایرکتوریِ فایلِ کمکیات اجرا کن.
python try_checkpointing.pynpx tsx try_checkpointing.tsمیبینی که ایجنت کامنتهای مستندسازی اضافه میکند، سپس درخواستی که میپرسد آیا میخواهی برگردانی. اگر بله را انتخاب کنی، فایل به حالتِ اصلیاش بازیابی میشود.
محدودیتها
Section titled “محدودیتها”File checkpointing این محدودیتها را دارد:
| محدودیت | توضیح |
|---|---|
| فقط ابزارهای Write/Edit/NotebookEdit | تغییراتِ انجامشده از طریقِ دستورهای Bash ردگیری نمیشوند |
| همان نشست | checkpointها به نشستی که آنها را ساخته گره خوردهاند |
| فقط محتوای فایل | ساختن، جابهجایی یا حذفِ دایرکتوریها با برگرداندن خنثی نمیشود |
| فایلهای محلی | فایلهای دور یا شبکهای ردگیری نمیشوند |
عیبیابی
Section titled “عیبیابی”گزینههای checkpointing شناخته نمیشوند
Section titled “گزینههای checkpointing شناخته نمیشوند”اگر enableFileCheckpointing یا rewindFiles() در دسترس نیست، ممکن است روی نسخهی قدیمیِ SDK باشی.
راهحل: به آخرین نسخهی SDK بهروزرسانی کن:
- Python:
pip install --upgrade claude-agent-sdk - TypeScript:
npm install @anthropic-ai/claude-agent-sdk@latest
پیامهای user UUID ندارند
Section titled “پیامهای user UUID ندارند”اگر message.uuid برابرِ undefined یا غایب است، checkpoint UUIDها را دریافت نمیکنی.
علت: گزینهی replay-user-messages تنظیم نشده.
راهحل: extra_args={"replay-user-messages": None} (Python) یا extraArgs: { 'replay-user-messages': null } (TypeScript) را به optionsات اضافه کن.
خطای «No file checkpoint found for message»
Section titled “خطای «No file checkpoint found for message»”این خطا وقتی رخ میدهد که دادهی checkpoint برای UUIDِ پیامِ userِ مشخصشده وجود ندارد.
علتهای رایج:
- file checkpointing روی نشستِ اصلی فعال نبود (
enable_file_checkpointingیاenableFileCheckpointingرویtrueتنظیم نشده بود) - نشست پیش از تلاش برای resume و برگرداندن، بهدرستی کامل نشده بود
راهحل: مطمئن شو که enable_file_checkpointing=True (Python) یا enableFileCheckpointing: true (TypeScript) روی نشستِ اصلی تنظیم شده بود، سپس از الگوی نشاندادهشده در مثالها استفاده کن: UUIDِ اولین پیامِ user را بگیر، نشست را کامل به پایان برسان، سپس با یک پرامپتِ خالی resume کن و rewindFiles() را یکبار فراخوانی کن.
خطای «ProcessTransport is not ready for writing»
Section titled “خطای «ProcessTransport is not ready for writing»”این خطا وقتی رخ میدهد که rewindFiles() یا rewind_files() را پس از اتمامِ پیمایشِ پاسخ فراخوانی کنی. اتصال به فرایندِ CLI وقتی حلقه کامل میشود بسته میشود.
راهحل: نشست را با یک پرامپتِ خالی resume کن، سپس rewind را روی query جدید فراخوانی کن:
# Resume session with empty prompt, then rewindasync with ClaudeSDKClient( ClaudeAgentOptions(enable_file_checkpointing=True, resume=session_id)) as client: await client.query("") async for message in client.receive_response(): await client.rewind_files(checkpoint_id) break// Resume session with empty prompt, then rewindconst rewindQuery = query({ prompt: "", options: { ...opts, resume: sessionId }});
for await (const msg of rewindQuery) { await rewindQuery.rewindFiles(checkpointId); break;}گامهای بعدی
Section titled “گامهای بعدی”- نشستها: یاد بگیر چطور نشستها را resume کنی، که برای برگرداندن پس از کاملشدنِ جریان لازم است. session IDها، resume کردنِ گفتگوها، و forkِ نشست را پوشش میدهد.
- دسترسیها: پیکربندی کن که Claude از کدام ابزارها بتواند استفاده کند و تغییراتِ فایل چطور تأیید شوند. وقتی میخواهی کنترلِ بیشتری روی زمانِ اعمالِ ویرایشها داشته باشی مفید است.
- مرجعِ TypeScript SDK: مرجعِ کاملِ API شاملِ همهی گزینههای
query()و متدِrewindFiles(). - مرجعِ Python SDK: مرجعِ کاملِ API شاملِ همهی گزینههای
ClaudeAgentOptionsو متدِrewind_files().