English ← MyDocs

文档索引

在此获取完整文档索引: https://code.claude.com/docs/llms.txt 使用此文件发现所有可用页面,然后再进一步探索。

使用检查点回退文件更改

跟踪智能体会话期间的文件更改,并将文件恢复到任何先前状态

文件检查点跟踪智能体会话期间通过 Write、Edit 和 NotebookEdit 工具进行的文件修改,允许您将文件回退到任何先前状态。想试试?跳转到交互式示例

使用检查点,您可以:

  • 撤消不需要的更改,将文件恢复到已知良好状态
  • 探索替代方案,恢复到检查点并尝试不同的方法
  • 从错误中恢复,当智能体进行了不正确的修改时
Warning

只有通过 Write、Edit 和 NotebookEdit 工具进行的更改会被跟踪。通过 Bash 命令(如 echo > file.txtsed -i)进行的更改不会被检查点系统捕获。

检查点的工作原理

当您启用文件检查点时,SDK 会在通过 Write、Edit 或 NotebookEdit 工具修改文件之前创建文件备份。响应流中的用户消息包含一个检查点 UUID,您可以用作恢复点。

检查点与智能体用于修改文件的以下内置工具配合工作:

工具描述
Write创建新文件或用新内容覆盖现有文件
Edit对现有文件的特定部分进行有针对性的编辑
NotebookEdit修改 Jupyter 笔记本(.ipynb 文件)中的单元格
Note

文件回退将磁盘上的文件恢复到先前状态。它不会回退对话本身。调用 rewindFiles()(TypeScript)或 rewind_files()(Python)后,对话历史和上下文保持不变。

检查点系统跟踪:

  • 会话期间创建的文件
  • 会话期间修改的文件
  • 被修改文件的原始内容

当您回退到检查点时,创建的文件会被删除,修改的文件会恢复到该时间点的内容。

实现检查点

要使用文件检查点,在选项中启用它,从响应流中捕获检查点 UUID,然后在需要恢复时调用 rewindFiles()(TypeScript)或 rewind_files()(Python)。

以下示例展示了完整流程:启用检查点,从响应流中捕获检查点 UUID 和会话 ID,然后稍后恢复会话以回退文件。每个步骤在下面详细解释。

import asyncio
from 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();
  1. 启用检查点

    配置您的 SDK 选项以启用检查点并接收检查点 UUID:

    选项PythonTypeScript描述
    启用检查点enable_file_checkpointing=TrueenableFileCheckpointing: true跟踪文件更改以进行回退
    接收检查点 UUIDextra_args={"replay-user-messages": None}extraArgs: { 'replay-user-messages': null }必需,以便在流中获取用户消息 UUID
    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 }
      }
    });
    
  2. 捕获检查点 UUID 和会话 ID

    设置 replay-user-messages 选项后(如上所示),响应流中的每个用户消息都有一个作为检查点的 UUID。

    对于大多数用例,捕获第一个用户消息 UUID(message.uuid);回退到它会将所有文件恢复到原始状态。要存储多个检查点并回退到中间状态,请参阅多个恢复点

    捕获会话 ID(message.session_id)是可选的;您只需要在流完成后稍后回退时才需要它。如果您在处理消息时立即调用 rewindFiles()(如在风险操作前设置检查点中的示例),可以跳过捕获会话 ID。

    checkpoint_id = None
    session_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_id
    
    let 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;
      }
    }
    
  3. 回退文件

    要在流完成后回退,请使用空提示恢复会话并使用检查点 UUID 调用 rewind_files()(Python)或 rewindFiles()(TypeScript)。您也可以在流期间回退;有关该模式,请参阅在风险操作前设置检查点

    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
    
    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;
    }
    

    如果您捕获了会话 ID 和检查点 UUID,也可以从 CLI 回退:

    claude -p --resume <session-id> --rewind-files <checkpoint-uuid>
    

常见模式

这些模式展示了根据您的用例捕获和使用检查点 UUID 的不同方式。

在风险操作前设置检查点

此模式仅保留最新的检查点 UUID,在每个智能体轮次之前更新它。如果在处理过程中出现问题,您可以立即回退到上一个安全状态并跳出循环。

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():
            # 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();

多个恢复点

如果 Claude 跨多轮进行更改,您可能想要回退到特定点而不是全部回退。例如,如果 Claude 在第一轮重构了文件并在第二轮添加了测试,您可能想要保留重构但撤消测试。

此模式将所有检查点 UUID 存储在带有元数据的数组中。会话完成后,您可以回退到任何先前的检查点:

import asyncio
from dataclasses import dataclass
from datetime import datetime
from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    UserMessage,
    ResultMessage,
)


# Store checkpoint metadata for better tracking
@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

    # 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 tracking
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;

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

试试看

此完整示例创建一个小型工具文件,让智能体添加文档注释,向您展示更改,然后询问您是否要回退。

开始之前,请确保您已安装 Claude Agent SDK

  1. 创建测试文件

    创建一个名为 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 / b
    
    export 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;
    }
    
  2. 运行交互式示例

    在与工具文件相同的目录中创建一个名为 try_checkpointing.py(Python)或 try_checkpointing.ts(TypeScript)的新文件,并粘贴以下代码。

    此脚本要求 Claude 为您的工具文件添加文档注释,然后给您回退和恢复原始文件的选项。

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

    此示例演示了完整的检查点工作流程:

    1. 启用检查点:使用 enable_file_checkpointing=Truepermission_mode="acceptEdits" 配置 SDK 以自动批准文件编辑
    2. 捕获检查点数据:在智能体运行时,存储第一个用户消息 UUID(您的恢复点)和会话 ID
    3. 提示回退:智能体完成后,检查您的工具文件以查看文档注释,然后决定是否要撤消更改
    4. 恢复并回退:如果是,使用空提示恢复会话并调用 rewind_files() 以恢复原始文件
  3. 运行示例

    从与工具文件相同的目录运行脚本。

    Tip

    在运行脚本之前,在 IDE 或编辑器中打开您的工具文件(utils.pyutils.ts)。您将看到文件在智能体添加文档注释时实时更新,然后在您选择回退时恢复到原始状态。

    python try_checkpointing.py
    
    npx tsx try_checkpointing.ts
    

    您将看到智能体添加文档注释,然后出现提示询问您是否要回退。如果选择是,文件将恢复到原始状态。

限制

文件检查点有以下限制:

限制描述
仅限 Write/Edit/NotebookEdit通过 Bash 命令进行的更改不会被跟踪
同一会话检查点绑定到创建它们的会话
仅文件内容创建、移动或删除目录不会被回退撤消
本地文件远程或网络文件不会被跟踪

故障排除

检查点选项未被识别

如果 enableFileCheckpointingrewindFiles() 不可用,您可能使用的是较旧的 SDK 版本。

解决方案:更新到最新的 SDK 版本:

  • Pythonpip install --upgrade claude-agent-sdk
  • TypeScriptnpm install @anthropic-ai/claude-agent-sdk@latest

用户消息没有 UUID

如果 message.uuidundefined 或缺失,您没有接收到检查点 UUID。

原因:未设置 replay-user-messages 选项。

解决方案:在选项中添加 extra_args={"replay-user-messages": None}(Python)或 extraArgs: { 'replay-user-messages': null }(TypeScript)。

"No file checkpoint found for message" 错误

当指定用户消息 UUID 的检查点数据不存在时会发生此错误。

常见原因

  • 原始会话未启用文件检查点(enable_file_checkpointingenableFileCheckpointing 未设置为 true
  • 在尝试恢复和回退之前,会话未正确完成

解决方案:确保在原始会话上设置了 enable_file_checkpointing=True(Python)或 enableFileCheckpointing: true(TypeScript),然后使用示例中显示的模式:捕获第一个用户消息 UUID,完整完成会话,然后使用空提示恢复并调用一次 rewindFiles()

"ProcessTransport is not ready for writing" 错误

当您在完成响应迭代后调用 rewindFiles()rewind_files() 时会发生此错误。当循环完成时,与 CLI 进程的连接会关闭。

解决方案:使用空提示恢复会话,然后在新查询上调用回退:

# Resume session with empty prompt, then rewind
async 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 rewind
const rewindQuery = query({
  prompt: "",
  options: { ...opts, resume: sessionId }
});

for await (const msg of rewindQuery) {
  await rewindQuery.rewindFiles(checkpointId);
  break;
}

后续步骤

  • 会话:了解如何恢复会话,这是在流完成后回退所必需的。涵盖会话 ID、恢复对话和会话分叉。
  • 权限:配置 Claude 可以使用哪些工具以及如何批准文件修改。如果您想要更多控制编辑发生的时间,这很有用。
  • TypeScript SDK 参考:完整的 API 参考,包括 query()rewindFiles() 方法的所有选项。
  • Python SDK 参考:完整的 API 参考,包括 ClaudeAgentOptionsrewind_files() 方法的所有选项。