【Claude Code 連載 第88回】ファイルチェックポイントで変更を巻き戻す(agent-sdk/file-checkpointing)

スポンサーリンク
【Claude Code 連載 第88回】ファイルチェックポイントで変更を巻き戻す(agent-sdk/file-checkpointing) 用語解説
【Claude Code 連載 第88回】ファイルチェックポイントで変更を巻き戻す(agent-sdk/file-checkpointing)
この記事は約30分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

スポンサーリンク

背景

この連載では、Claude Codeの公式ドキュメントを1ページずつ読み解いていきます。公式ドキュメントは情報が網羅されている分、「結局どの機能を、どんな場面で使えばいいのか」は自分で考える必要があり、読むのに意外と時間がかかります。そこで、私が実務で使うために読み込んだ内容を「使う場面→実例コード」の順に整理して残していくことにしました。専門家の解説というより、一次情報を読んだ記録です。推測や動作を確認していない部分には、その都度そう書きます。

公式ドキュメント:https://code.claude.com/docs/ja/agent-sdk/file-checkpointing

1. 一言でいうと

ファイルチェックポイントは、エージェントが作業中に書き換えたファイルを、あとから以前の任意の時点の状態に戻せるようにする仕組みです。SDK は、エージェントが Write・Edit・NotebookEdit ツールでファイルを変える前に、そのファイルのバックアップを取っておきます。

前回(第87回)で、「セッションが保持するのは会話で、ファイルではない」「fork してもファイルは分岐しない」と繰り返し書きました。そのたびに送り先になっていたのがこのページです。CLI では第69回で扱った巻き戻し(チェックポイント)の機能を、SDK から使う方法にあたります。

大事な制限を先に書いておきます。追跡されるのは Write・Edit・NotebookEdit ツールでの変更だけです。 Bash コマンド(echo > file.txt や sed -i など)での変更は記録されません。サブエージェントが行った編集も、一部の例外を除いて記録されません。


2. どういう場面で役立つか

シーン1:エージェントの修正が期待外れだったので元に戻す

「認証モジュールをリファクタリングして」と任せた結果が気に入らなかった場合に、ファイルを作業前の状態に戻します。最初のチェックポイントに戻せば、エージェントが作ったファイルは消え、書き換えたファイルは元の中身に戻ります。

シーン2:検証に失敗したら、その場で直前の状態に戻す

エージェントの作業中にテストや検証を走らせ、失敗したらすぐに直前の安全な状態へ戻して処理を止める、という自動化です。ターンごとに最新のチェックポイントを更新しておき、問題が起きたらそこに戻します。

シーン3:途中までの変更は残し、後半だけ取り消す

1ターン目でリファクタリング、2ターン目でテストの追加、という作業のうち、リファクタリングは残してテストの追加だけを取り消したい場合です。ターンごとのチェックポイントを全部記録しておけば、好きな時点に戻せます。

不要・向かないケース

  • Bash コマンドでファイルを変える作業:記録されないので戻せません。
  • サブエージェントに編集させる作業:サブエージェントの編集は記録されません(例外は後述)。git で戻します。
  • ディレクトリの作成・移動・削除を戻したい:巻き戻しは中身だけで、ディレクトリの操作は戻しません。
  • 会話も一緒に巻き戻したい:ファイルの巻き戻しは会話を戻しません。会話の分岐は第87回の fork の役割です。
  • 長く残る変更履歴がほしい:それは git の役割です(第69回の「チェックポイントはその場の取り消し、git は永続的な履歴」)。

3. コードと仕組みの解説

原文の fenced コードブロックは22本です。次のように切り分けます。

  • 引用(7本):実装の流れ全体を示す完全な例(対訳2本)、CLI から巻き戻すコマンド(1本)、「リスクのある操作の前にチェックポイント」(対訳2本)、「複数の復元ポイント」(対訳2本)。
  • 表と散文に圧縮(15本):
    • 実装手順の各ステップに置かれた断片(対訳3組6本):どれも完全な例から該当部分を切り出したものです。
    • 「試してみる」の6本:題材のファイル(utils.py/utils.ts)、対話式のスクリプト(try_checkpointing.py/.ts)、実行コマンド2本。スクリプトは完全な例に y/n の確認を足したものです。
    • トラブルシューティングにある CLI コマンドの再掲1本と、巻き戻しの対訳2本(完全な例と同じ形)。

違う部分は散文で説明します。引用したコードは原文から機械的に抜き出したもので、入れ子に由来する先頭インデントを外した以外は変えていません。

3-0. 前提と制限

バージョン要件は1つです。

挙動必要バージョン
巻き戻しのときに、シンボリックリンク・ハードリンクなどの通常以外のファイルを飛ばす(それ以前は、リンクを通じて書き込みや削除をしていた)Claude Code v2.1.216 以降

制限事項は次の5つです(原文の表)。

制限内容
Write・Edit・NotebookEdit ツールのみBash コマンドでの変更は追跡されない
サブエージェントの編集サブエージェントの編集は追跡も復元もされない。ただし context: fork を使ってフォアグラウンドで動くスキルは例外。追跡されない編集は git で戻す
同じセッションチェックポイントは、それを作ったセッションに結び付いている
ファイルの中身のみディレクトリの作成・移動・削除は、巻き戻しで元に戻らない
ローカルのファイルリモートやネットワーク上のファイルは追跡されない

acceptEdits と組み合わせるときの注意。 原文の例は全て permissionMode: "acceptEdits" で、ファイル編集を確認なしで進めています。チェックポイントがあるので安心して任せられる、という組み合わせです。ただし第80回で見たとおり、acceptEdits はファイル編集に加えて mkdir・touch・rm・rmdir・mv・cp・sed といったファイル操作のシェルコマンドも自動で承認します。これらは Bash での変更なので、チェックポイントには記録されません。rm で消されたファイルや mv で動かされたファイルは、巻き戻しでは戻らないということです。チェックポイントを安全網にするなら、このことを前提に、git での退避や、フックで rm などを止める設定(第82回)を合わせて考える必要があります(第80回の内容との組み合わせによる筆者の指摘)。

3-1. 仕組み

ファイルチェックポイントを有効にすると、SDK は Write・Edit・NotebookEdit ツールでファイルを変える前に、そのファイルのバックアップを作ります。応答のストリームに流れてくるユーザーメッセージには、チェックポイントの UUID が付いていて、これを復元の目印に使います。

巻き戻すのはディスク上のファイルだけで、会話は戻りません。 rewindFiles()(TypeScript)か rewind_files()(Python)を呼んだあとも、会話の履歴と文脈はそのまま残ります。

チェックポイントに巻き戻すと、次のことが起きます。

  • エージェントが作ったファイルは削除される
  • エージェントが書き換えたファイルは、その時点の中身に戻される

ただし、次のものは飛ばされます(v2.1.216 以降)。

  • シンボリックリンク、ハードリンク、その他の通常以外のファイルである追跡対象のパス
  • 親ディレクトリが、チェックポイントの時点と同じ場所を指さなくなった追跡対象のファイル
  • バックアップを安全に読めない追跡対象のファイル

飛ばされたパスの数は、結果の RewindFilesResult の skippedLinks フィールドに入ります。第71回で「CLI のチェックポイントはシンボリックリンクやハードリンクを復元しない」と見ましたが、SDK でも同じ扱いです。リンクを通じた書き込みで意図しない場所のファイルを壊さないための安全策と読めます(筆者の読み)。

CLI との違い。 第69回の CLI の巻き戻しメニューでは、コードを戻す・会話を戻す・両方を戻す、を選べました。SDK の rewindFiles() はファイルだけです。会話も分岐させたいなら、第87回の fork と組み合わせます(組み合わせ方の提案は筆者)。

3-2. 実装の流れ

使い方は3段階です。オプションで有効にし、応答のストリームからチェックポイントの UUID を記録し、戻したいときに rewindFiles()/rewind_files() を呼びます。

次の例は、その流れ全体を示しています。チェックポイントを有効にし、ストリームからチェックポイントの UUID とセッション ID を記録し、あとでセッションを再開してファイルを巻き戻します。プロンプトは「認証モジュールをリファクタリングして」なので、認証モジュールがあるプロジェクトで動かすか、プロジェクトにあるファイルを指すようにプロンプトを変えます。ファイルが変わり、巻き戻しで元に戻るのを確かめられます。

import asyncio
from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    UserMessage,
    ResultMessage,
)


async def main():
    # Step 1: checkpointing を有効にする
    options = ClaudeAgentOptions(
        enable_file_checkpointing=True,
        permission_mode="acceptEdits",  # プロンプトなしでファイル編集を自動承認
        extra_args={
            "replay-user-messages": None
        },  # レスポンスストリームで checkpoint UUID を受け取るために必須
    )

    checkpoint_id = None
    session_id = None

    # クエリを実行し、checkpoint UUID とセッション ID をキャプチャ
    async with ClaudeSDKClient(options) as client:
        await client.query("Refactor the authentication module")

        # Step 2: 最初のユーザーメッセージから checkpoint UUID をキャプチャ
        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: 後で、空のプロンプトでセッションを再開して巻き戻す
    if checkpoint_id and session_id:
        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
        print(f"Rewound to checkpoint: {checkpoint_id}")


asyncio.run(main())
import { query } from "@anthropic-ai/claude-agent-sdk";

async function main() {
  // Step 1: checkpointing を有効にする
  const opts = {
    enableFileCheckpointing: true,
    permissionMode: "acceptEdits" as const, // プロンプトなしでファイル編集を自動承認
    extraArgs: { "replay-user-messages": null } // レスポンスストリームで checkpoint UUID を受け取るために必須
  };

  const response = query({
    prompt: "Refactor the authentication module",
    options: opts
  });

  let checkpointId: string | undefined;
  let sessionId: string | undefined;

  // Step 2: 最初のユーザーメッセージから checkpoint UUID をキャプチャ
  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) {
    // A single-shot query() throws after yielding an error result. If the
    // failure was an error result, sessionId and checkpointId were already
    // captured by the loop above; connection or process failures yield no
    // result message.
    console.error(`Session ended with an error: ${error}`);
  }

  // Step 3: 後で、空のプロンプトでセッションを再開して巻き戻す
  if (checkpointId && sessionId) {
    const rewindQuery = query({
      prompt: "", // 接続を開くための空のプロンプト
      options: { ...opts, resume: sessionId }
    });

    for await (const msg of rewindQuery) {
      await rewindQuery.rewindFiles(checkpointId);
      break;
    }
    console.log(`Rewound to checkpoint: ${checkpointId}`);
  }
}

main();

以下、ステップごとに見ていきます。

ステップ1:チェックポイントを有効にする

必要なオプションは2つです。

目的PythonTypeScript説明
チェックポイントを有効にするenable_file_checkpointing=TrueenableFileCheckpointing: true巻き戻しのためにファイルの変更を追跡する
チェックポイントの UUID を受け取るextra_args={"replay-user-messages": None}extraArgs: { 'replay-user-messages': null }ストリームの中でユーザーメッセージの UUID を受け取るために必須

2つ目を忘れると、有効にしていても UUID が受け取れず、巻き戻す先を指定できません。 replay-user-messages は、extra_args/extraArgs で CLI に追加の引数として渡すものです。値は Python では None、TypeScript では null で、「値を持たないフラグ」を意味します。

ステップ2:チェックポイントの UUID とセッション ID を記録する

replay-user-messages を設定すると、ストリームの各ユーザーメッセージに、チェックポイントとして使える UUID が付きます。

  • 多くの場合は、最初のユーザーメッセージの UUID(message.uuid)を記録します。 これに戻すと、全てのファイルが作業前の状態に戻ります。途中の状態に戻したい場合は、3-3 の「複数の復元ポイント」を使います。
  • セッション ID(message.session_id)の記録は任意です。 ストリームが終わった後で巻き戻したい場合にだけ必要です。3-3 の「リスクのある操作の前に」のように、メッセージを処理している最中にすぐ rewindFiles() を呼ぶなら不要です。

原文のステップ2の断片は完全な例の該当部分と同じ形で、違いはセッション ID の取り方だけです。Python は結果メッセージ(ResultMessage)から、TypeScript は session_id を持つどのメッセージからでも取っています。

ステップ3:ファイルを巻き戻す

ストリームが終わった後で巻き戻すには、空のプロンプトでセッションを再開し、チェックポイントの UUID を渡して rewind_files()(Python)/rewindFiles()(TypeScript)を呼びます。完全な例の Step 3 の部分がこれにあたり、再開した問い合わせの最初のメッセージを受け取ったところで巻き戻しを呼び、すぐにループを抜けています。空のプロンプトは「接続を開くため」とコメントにあります。原文のステップ3の断片は、巻き戻しの前に if checkpoint_id: で UUID の有無を確かめている点だけが違います。

再開するセッションでもチェックポイントを有効にしている点に注意してください。Python の例は ClaudeAgentOptions(enable_file_checkpointing=True, resume=session_id)、TypeScript の例は { ...opts, resume: sessionId } で、元の設定を引き継いでいます。これを忘れると、3-5 の「File rewinding is not enabled」エラーになります。

なお、空のプロンプトで再開したときに Claude への問い合わせが1回分発生するのかどうかは、原文からは分かりません(CLI の方法については「プロンプトを送らずに終了する」と明記されています)。

CLI から巻き戻す。 セッション ID とチェックポイントの UUID を記録していれば、CLI からも巻き戻せます。

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

このコマンドには claude の実行ファイルが必要です。これは SDK のパッケージには入っていないので、Claude Code を別にインストールします(第78回で見たように、SDK は Claude Code のバイナリを同梱していますが、コマンドとして使える claude は別です)。SDK はチェックポイントを内部で有効にしますが、claude -p を直接動かす場合は、環境変数 CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING を設定する必要があります。

--rewind-files フラグは claude --help には表示されませんが、上のように書けば受け付けられます。成功すると Files rewound to state at message <checkpoint-uuid> と表示され、プロンプトを送らずに終了します。

3-3. よく使うパターン

リスクのある操作の前にチェックポイントを取る

最新のチェックポイントの UUID だけを持ち、各ターンの前に更新していきます。処理中に問題が起きたら、最後の安全な状態にすぐ戻してループを抜けます。

動かす前に、your_revert_condition(Python)/yourRevertCondition(TypeScript)を、自分の判定(エラーの検出や検証の失敗など)に置き換えます。サンプルではこの名前は定義されていません。

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():
            # 各エージェントターンが開始する前に checkpoint を更新
            # これは前の checkpoint を上書きします。最新のみを保持
            if isinstance(message, UserMessage) and message.uuid:
                safe_checkpoint = message.uuid

            # 独自のロジックに基づいて復帰するかどうかを決定
            # 例:エラー検出、検証失敗、またはユーザー入力
            if your_revert_condition and safe_checkpoint:
                await client.rewind_files(safe_checkpoint)
                # 巻き戻し後、ループを終了します。ファイルは復元されます
                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) {
    // 各エージェントターンが開始する前に checkpoint を更新
    // これは前の checkpoint を上書きします。最新のみを保持
    if (message.type === "user" && message.uuid) {
      safeCheckpoint = message.uuid;
    }

    // 独自のロジックに基づいて復帰するかどうかを決定
    // 例:エラー検出、検証失敗、またはユーザー入力
    if (yourRevertCondition && safeCheckpoint) {
      await response.rewindFiles(safeCheckpoint);
      // 巻き戻し後、ループを終了します。ファイルは復元されます
      break;
    }
  }
}

main();

このパターンでは、ストリームの最中に巻き戻すので、セッション ID を記録して再開する必要がありません。TypeScript では、最初の query() の戻り値 response に対して rewindFiles() を呼んでいます。接続がまだ開いているので、そのまま巻き戻せます。

チェックポイントは変更の前に取られるバックアップなので、ユーザーメッセージの UUID は「そのターンで何かを変える前の状態」を指します。「最新のチェックポイントに戻す」とは、直前のターンの変更を取り消すことです(原文の「各エージェントターンが開始する前に checkpoint を更新」という説明からの筆者の整理)。

複数の復元ポイント

Claude が何ターンもかけて変更する場合、全部を戻すのではなく、特定の時点に戻したいことがあります。たとえば1ターン目でリファクタリング、2ターン目でテストの追加をしたとき、リファクタリングは残してテストだけを取り消す、という場合です。

次のパターンは、全てのチェックポイントの UUID を、説明と時刻を付けて配列に保存します。セッションが終わった後で、どの時点にでも戻せます。

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


# より良い追跡のために checkpoint メタデータを保存
@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

    # 後で:セッションを再開して任意の checkpoint に巻き戻す
    if checkpoints and session_id:
        target = checkpoints[0]  # 任意の checkpoint を選択
        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(target.id)
                break
        print(f"Rewound to: {target.description}")


asyncio.run(main())
import { query } from "@anthropic-ai/claude-agent-sdk";

// より良い追跡のために checkpoint メタデータを保存
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) {
    // 単一ショットの query() はエラー結果を生成した後にスローします。失敗がエラー結果だった場合、sessionId とチェックポイント配列は上記のループによってすでに入力されています。接続またはプロセスの失敗は結果メッセージを生成しません。
    console.error(`Session ended with an error: ${error}`);
  }

  // 後で:セッションを再開して任意の checkpoint に巻き戻す
  if (checkpoints.length > 0 && sessionId) {
    const target = checkpoints[0]; // 任意の checkpoint を選択
    const rewindQuery = query({
      prompt: "", // 接続を開くための空のプロンプト
      options: { ...opts, resume: sessionId }
    });

    for await (const msg of rewindQuery) {
      await rewindQuery.rewindFiles(target.id);
      break;
    }
    console.log(`Rewound to: ${target.description}`);
  }
}

main();

例では checkpoints[0](最初の時点)を選んでいますが、コメントのとおり配列のどれでも選べます。説明は After turn 1 のように自動で付けていますが、実際のアプリでは、そのターンで Claude が何をしたかを説明に入れておくと、戻す先を選びやすくなります(筆者の提案)。

言語の違いは、チェックポイントの記録の形です。Python は @dataclass の Checkpoint、TypeScript は interface Checkpoint で、どちらも id・description・timestamp の3項目です。

3-4. 試してみる

原文には、手元で動かして確かめるための一式があります。完全な例と同じ流れなので、コードは引用せず内容をまとめます。

手順内容
1. 題材のファイルを作るutils.py(Python)か utils.ts(TypeScript)に、足し算・引き算・掛け算・割り算の4つの関数を書く(割り算は0で割るとエラーを出す)
2. スクリプトを作る同じディレクトリに try_checkpointing.py/try_checkpointing.ts を作る。中身は完全な例と同じ流れで、プロンプトが「utils にドキュメントコメントを付けて」になり、終わった後に「巻き戻してコメントを消しますか?(y/n)」と聞く
3. 実行するPython は python try_checkpointing.py、TypeScript は npx tsx try_checkpointing.ts

実行する前に、題材のファイルをエディタで開いておくと、エージェントがコメントを書き足す様子がリアルタイムで見え、巻き戻しを選ぶと元に戻るのが確かめられます。始める前に Agent SDK をインストールしておきます(第78回)。

3-5. トラブルシューティング

症状原因対処
enableFileCheckpointing や rewindFiles() が見つからないSDK のバージョンが古いPython は pip install --upgrade claude-agent-sdk、TypeScript は npm install @anthropic-ai/claude-agent-sdk@latest で更新する
ユーザーメッセージに UUID が無い(message.uuid が undefined など)replay-user-messages を設定していないextra_args={"replay-user-messages": None}(Python)/extraArgs: { 'replay-user-messages': null }(TypeScript)を足す
No file checkpoint found for this message元のセッションでチェックポイントを有効にしていない/セッションがきちんと終わる前に再開して巻き戻そうとした元のセッションで有効にしたうえで、最初のユーザーメッセージの UUID を記録し、セッションを最後まで終わらせ、空のプロンプトで再開して rewindFiles() を1回呼ぶ
File rewinding is not enabledチェックポイントを有効にしていない状態で、非対話の巻き戻しをしようとした(素の claude -p --rewind-files、または有効化していない SDK セッション。再開したセッションも含む)CLI では CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING=true を付けて実行する(3-2 のコマンドと同じ)。SDK では再開するセッションでも有効にする
ProcessTransport is not ready for writing応答を最後まで読み終えた後で rewindFiles() を呼んだ。ループが終わると CLI のプロセスとの接続が閉じる空のプロンプトでセッションを再開し、その新しい問い合わせで巻き戻す

4行目の原因について、原文は仕組みも説明しています。SDK が内部で環境変数 CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING を設定するのは、巻き戻しをするセッションでチェックポイントが有効になっているときだけです。素の CLI はこれを設定しません。だから、再開したセッションでも有効にする必要があり、CLI では自分で環境変数を付ける必要があるのです。

5行目は、3-2 のステップ3で「ストリームが終わった後は空のプロンプトで再開する」と書いた理由そのものです。原文の対処のコードは完全な例の巻き戻し部分と同じ形で、TypeScript 版には巻き戻しを try で囲み、「ここでエラーが出たら、チェックポイントが見つからなかったかセッションを再開できなかったなど、巻き戻しは完了していない」というコメントが付いています。巻き戻しが失敗しうる前提で、失敗をアプリ側で検知できるようにしておくのが大事です。


4. まとめ

  • ファイルチェックポイントは、Write・Edit・NotebookEdit で変える前にバックアップを取り、あとで任意の時点に戻せる仕組み。戻すのはファイルだけで、会話は戻らない。
  • 有効にするには enableFileCheckpointing: true(Python は enable_file_checkpointing=True)と、UUID を受け取るための replay-user-messages の両方が要る。
  • チェックポイントの目印はユーザーメッセージの UUID。最初のものに戻せば作業前の状態、途中のものに戻せばその時点の状態になる。
  • 巻き戻すと、作ったファイルは消え、書き換えたファイルは元に戻る。リンクや通常以外のファイルなどは飛ばされ、数は skippedLinks に入る(v2.1.216 以降)。
  • ストリームの最中なら、そのまま rewindFiles() を呼べる。終わった後は、空のプロンプトでセッションを再開して呼ぶ(再開側でもチェックポイントを有効にする)。CLI からも、環境変数 CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING を付けて --rewind-files で巻き戻せる(3-2 のコマンド。claude は別途インストール)。
  • 制限:Bash での変更は戻らない(acceptEdits が自動承認する rm・mv なども)、サブエージェントの編集も戻らない(フォアグラウンドの context: fork スキルは例外)、ディレクトリ操作は戻らない、同じセッション限定、ローカルのファイル限定。
  • 永続的な履歴や、チェックポイントで戻らない変更には git を使う。

次回予告

次回は 「サブエージェント」(agent-sdk/subagents) を取り上げる予定です。

今回、サブエージェントの編集はチェックポイントに記録されないと見ました。第80回では、サブエージェントが親の権限モードを引き継ぐことも見ています。次回は、SDK のエージェントから作業の一部を別のエージェントに任せる仕組みそのものを取り上げ、どう定義し、どう呼び出し、親とどう情報をやり取りするのかを見ていきます。

※テーマは変更になる場合があります。


よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました