Skip to content

Error Handling and Recovery

github-actions[bot] edited this page Mar 14, 2026 · 1 revision

Error-Handling-and-Recovery

wikigenのエラーハンドリングおよびリカバリー機能について詳細に説明するページである。Wikiページ生成は外部プロセス(Claude CLI)の呼び出しを伴うため、部分的な失敗が発生し得る。本ページでは、自動リトライ機構(1ページあたり最大3回)、_errors.logへのエラーログ記録、失敗ページの検出方法、および-retryフラグを用いた部分的な失敗からのリカバリー手順を解説する。システム全体の構造についてはArchitecture and Designを、CLI全般についてはCLI Reference and Usageを参照のこと。


エラーハンドリングの全体像

wikigenのエラーハンドリングは、複数の層で構成されている。入力バリデーション、Gitクローン、Claude CLI呼び出し、ページ生成の各段階でエラーを適切に捕捉し、可能な限り処理を継続する「フォルトトレラント」な設計となっている。

flowchart TD
    A[入力受付] --> B[バリデーション]
    B -->|エラー| C[即時終了 / エラーメッセージ]
    B -->|OK| D[リポジトリクローン]
    D -->|エラー| E[プロジェクト失敗<br/>他プロジェクト継続]
    D -->|OK| F[構造決定フェーズ]
    F -->|エラー| E
    F -->|OK| G[ページ生成ループ]
    G --> H{生成成功?}
    H -->|成功| I[ファイル保存<br/>次のページへ]
    H -->|失敗| J{リトライ残あり?}
    J -->|はい| K[ファイル削除<br/>再試行]
    K --> H
    J -->|なし| L[_errors.log 記録<br/>プレースホルダー作成]
    L --> I
    I --> M{全ページ完了?}
    M -->|いいえ| G
    M -->|はい| N[結果集計<br/>JSON出力]
Loading

Sources: main.go:563-636


入力バリデーション

リポジトリ名の受け付け時に、フォーマット検証・パストラバーサル防止・シェルインジェクション防止の3種類のバリデーションが実施される。

var validRepoPattern = regexp.MustCompile(`^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$`)

func validateRepo(repo string) error {
	if !validRepoPattern.MatchString(repo) {
		return fmt.Errorf("invalid repo format: %q (expected owner/repo)", repo)
	}
	if strings.Contains(repo, "..") {
		return fmt.Errorf("path traversal detected in repo: %q", repo)
	}
	if strings.ContainsAny(repo, ";&|`$(){}[]!~") {
		return fmt.Errorf("invalid characters in repo: %q", repo)
	}
	return nil
}

Sources: main.go:79-92

バリデーションルール一覧

チェック種別 検査内容 エラーメッセージ例
フォーマット検証 owner/repo 形式のみ許可 invalid repo format: "foo" (expected owner/repo)
パストラバーサル .. を含む場合拒否 path traversal detected in repo: "../etc"
シェルインジェクション ;, &, |, `, $, (, ), {, }, [, ], !, ~ を拒否 invalid characters in repo: "foo/bar;rm"

エラーログ(_errors.log)

ログファイルの概要

ページ生成に失敗した場合、wiki-output/{project}/_errors.log へタイムスタンプ付きのエラーメッセージが追記される。このファイルはエラー発生時に自動作成され、エラーが発生しなければ作成されない。

func appendError(dir, msg string) {
	errFile := filepath.Join(dir, "_errors.log")
	f, err := os.OpenFile(errFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		return
	}
	defer f.Close()
	fmt.Fprintf(f, "[%s] %s\n", time.Now().Format("15:04:05"), msg)
}

Sources: main.go:462-470

ログエントリの形式

[15:04:05] Page 3/12: API-Specification — failed after 3 attempts
[15:07:22] Page 8/12: Data-Model — failed after 3 attempts
  • タイムスタンプ形式: HH:MM:SS
  • 内容: ページ番号/総ページ数、ページタイトル、失敗回数

出力ディレクトリ構造

wiki-output/
└── {project}/
    ├── Home.md
    ├── _Sidebar.md
    ├── System-Overview.md
    ├── API-Specification.md      ← 生成済みページ
    ├── Data-Model.md             ← プレースホルダー(失敗時)
    └── _errors.log               ← 失敗時のみ作成

Sources: main.go:462-470, main.go:608-609


自動リトライ機構

リトライロジックの概要

ページ生成ゴルーチン内で、1ページにつき最大3回の試行が行われる。

maxRetries := 3
var success bool

for attempt := 1; attempt <= maxRetries; attempt++ {
    if attempt > 1 {
        progress.set(projectName, fmt.Sprintf("🔄 %d/%d %s (retry %d)", idx+1, len(allPages), page.Title, attempt))
    }

    // 前回の失敗ファイルを削除
    os.Remove(filename)

    _, err := claudeCall(claudePath, model, repoDirs, "", pagePrompt(*page, allPages, projectName, repos, language), wikiDir)
    if err != nil {
        continue
    }

    // Claudeがファイルを書き込んだか確認
    written, readErr := os.ReadFile(filename)
    if readErr == nil && len(written) > 100 {
        page.Content = string(written)
        success = true
        break
    }
}

Sources: main.go:583-605

成功判定条件

試行が「成功」と判定されるためには、以下の両条件を満たす必要がある。

条件 詳細
Claude CLIの呼び出し成功 claudeCall() がエラーを返さないこと
ファイルの書き込み確認 出力ファイルが存在し、サイズが100バイト超であること

リトライフローチャート

flowchart TD
    A[attempt = 1] --> B[前回ファイル削除]
    B --> C[claudeCall 実行]
    C --> D{エラー?}
    D -->|はい| E{attempt < 3?}
    E -->|はい| F[attempt++]
    F --> B
    E -->|いいえ| G[失敗確定]
    D -->|いいえ| H[ファイル読み込み]
    H --> I{サイズ > 100?}
    I -->|はい| J[success = true<br/>ループ終了]
    I -->|いいえ| E
    G --> K[_errors.log 記録]
    K --> L[プレースホルダー作成]
Loading

Sources: main.go:583-615

全試行失敗時の処理

3回すべての試行が失敗した場合、以下の処理が実行される。

  1. _errors.log へエラーを記録
  2. プレースホルダーファイルを作成(Wikiの構造を維持するため)
if !success {
    appendError(wikiDir, fmt.Sprintf("Page %d/%d: %s — failed after %d attempts", idx+1, len(allPages), page.Title, maxRetries))
    os.WriteFile(filename, []byte(fmt.Sprintf("# %s\n\n*Content generation failed after %d attempts*\n", page.Title, maxRetries)), 0644)
}

Sources: main.go:607-610

プレースホルダーの内容:

# Page-Title

*Content generation failed after 3 attempts*

失敗ページの検出

ページ生成完了後の集計

全ページの生成が完了した後、ファイルサイズに基づいて各ページのステータスが確定される。

var failedCount int
for i, p := range allPages {
    fileInfo, err := os.Stat(filepath.Join(wikiDir, p.Filename+".md"))
    if err != nil || fileInfo.Size() < 200 {
        result.Pages[i].Status = "failed"
        result.Pages[i].Size = 0
        failedCount++
    } else {
        result.Pages[i].Status = "ok"
        result.Pages[i].Size = int(fileInfo.Size())
    }
}
result.Failed = failedCount

Sources: main.go:617-631

判定基準

状態 条件 Status
成功 ファイルが存在し、サイズ ≧ 200バイト "ok"
失敗 ファイルが存在しない、またはサイズ < 200バイト "failed"

注意: 生成時の成功判定(100バイト超)と、集計時の失敗判定(200バイト未満)は異なる閾値を用いる。プレースホルダーファイルは約50バイト程度であるため、200バイト未満として失敗判定される。

JSONレポートにおけるエラー情報

JSON Output and Integration で詳述されているように、-jsonフラグ使用時には失敗情報が構造化データとして出力される。

type WikiResult struct {
    Project    string           `json:"project"`
    Pages      []WikiPageResult `json:"pages"`
    TotalPages int              `json:"total_pages"`
    Failed     int              `json:"failed"`
    Status     string           `json:"status"`
}

type WikiPageResult struct {
    Title    string `json:"title"`
    Filename string `json:"filename"`
    Size     int    `json:"size"`
    Status   string `json:"status"`
}

Sources: main.go:96-112

ステータス値一覧

ステータス値 意味
"pending" 生成待ち
"ok" 正常生成完了(サイズ ≧ 200バイト)
"failed" 生成失敗(全リトライ消費後)
"completed" Wiki全体の生成完了
"error" 致命的エラーが発生
"dry-run" 構造確認のみの実行

-retryフラグによるリカバリー

概要

-retryフラグを使用すると、wiki-output/ディレクトリ内の既存の成功したページを保持しつつ、失敗したページのみを再生成できる。

if retryFailed {
    retryFailedPages(claudePath, model, language, outputDir, cloneDir, pageParallel)
    return
}

Sources: main.go:935-939

使用方法

# 通常の生成(一部ページが失敗したとする)
wikigen -repo owner/repo

# 失敗したページのみ再生成
wikigen -retry

詳細なフラグオプションについてはCLI Reference and Usageを参照のこと。

retryFailedPages 関数の処理フロー

sequenceDiagram
    participant Main as main()
    participant Retry as retryFailedPages()
    participant FS as ファイルシステム
    participant Claude as Claude CLI

    Main->>Retry: retryFailedPages() 呼び出し
    Retry->>FS: wiki-output/ ディレクトリ読み取り
    FS-->>Retry: プロジェクトディレクトリ一覧
    loop 各プロジェクト
        Retry->>FS: *.md ファイル一覧取得
        loop 各 .md ファイル
            Retry->>FS: ファイル内容読み取り
            FS-->>Retry: ファイル内容
            Retry->>Retry: 失敗判定<br/>("Content generation failed" または < 200バイト)
        end
        Retry->>FS: Home.md 読み取り(コンテキスト取得)
        loop 各失敗ページ
            Retry->>FS: 失敗ファイル削除
            Retry->>Claude: claudeCall() 実行
            Claude-->>Retry: 生成結果
            Retry->>FS: ファイル存在・サイズ確認
        end
    end
    Retry-->>Main: 完了
Loading

Sources: main.go:741-875

失敗ページの識別方法(retryモード)

retryFailedPages()関数では、以下の条件でファイルを失敗とみなす。

if strings.Contains(string(content), "Content generation failed") || len(content) < 200 {
    // 失敗リストに追加
}

Sources: main.go:771

識別条件 説明
"Content generation failed" を含む 前回の実行でプレースホルダーが作成されたページ
ファイルサイズ < 200バイト 内容が極端に少ないページ(プレースホルダー含む)

コンテキストの再構築

リトライ時は、Home.mdのリンク行から各ページの説明(description)を抽出し、ページ生成プロンプトに活用する。

desc := ""
for _, line := range strings.Split(homeStr, "\n") {
    if strings.Contains(line, fmt.Sprintf("[%s]", page.title)) || strings.Contains(line, fmt.Sprintf("(%s)", page.filename)) {
        if idx := strings.Index(line, "— "); idx != -1 {
            desc = line[idx+len("— "):]
        }
        break
    }
}

Sources: main.go:820-829


Claude CLI呼び出しのエラーハンドリング

claudeCall 関数

Claude CLIの実行エラーは、コマンドのエラーとstderrの両方を含むエラーメッセージとして返される。

func claudeCall(claudePath, model string, repoDirs []string, systemPrompt, prompt, workDir string) (string, error) {
    // ... コマンド構築 ...
    if err := cmd.Run(); err != nil {
        return "", fmt.Errorf("claude: %v\nstderr: %s", err, stderr.String())
    }
    return strings.TrimSpace(stdout.String()), nil
}

Sources: main.go:116-143


プロジェクトレベルのフォルトトレランス

複数リポジトリを処理する場合、あるプロジェクトが失敗しても他のプロジェクトの処理は継続される。

result, err := generateWiki(/* ... */)
mu.Lock()
if err != nil {
    log.Printf("[%s] ❌ %v", t.name, err)
    progress.done(t.name)
    failed = append(failed, t.name)
    if result != nil {
        result.Status = "error"
    }
}
mu.Unlock()

Sources: main.go:1013-1034

エラーの伝播経路

flowchart TD
    A[バリデーションエラー] -->|即時終了| B[プログラム終了]
    C[Gitクローンエラー] -->|プロジェクト失敗| D[failed リストに追加]
    E[構造決定エラー] -->|プロジェクト失敗| D
    F[ページ生成エラー] -->|リトライ後失敗| G[_errors.log 記録<br/>プレースホルダー作成]
    G --> H[次のページへ継続]
    D --> I[他プロジェクト処理継続]
Loading

トラブルシューティングガイド

_errors.log の確認方法

# エラーログの確認
cat wiki-output/{project}/_errors.log

# 出力例
[15:04:05] Page 3/12: API-Specification — failed after 3 attempts

よくある問題と対処法

症状 原因 対処法
_errors.log が存在する ページ生成失敗 -retryフラグで再実行
プレースホルダーページが多い Claude CLI の不安定 -retryを繰り返し実行
invalid repo format エラー リポジトリ名の形式不正 owner/repo 形式で指定
no pages found in structure 構造決定フェーズの失敗 Claude CLIの動作確認、再実行
clone エラー Git認証エラー SSH鍵またはGITHUB_TOKENの確認
ページサイズが小さい(< 200バイト) 内容生成の失敗 -retryフラグで再実行

失敗件数の確認(JSON出力)

# 失敗ページ数の確認
wikigen -repo owner/repo -json | jq '.failed'

# 失敗したページの一覧
wikigen -repo owner/repo -json | jq '.pages[] | select(.status=="failed") | .title'

詳細はJSON Output and Integrationを参照のこと。


エラーハンドリングのまとめ

classDiagram
    class ErrorHandler {
        +validateRepo(repo) error
        +appendError(dir, msg)
        +claudeCall() (string, error)
    }
    class RetryMechanism {
        +maxRetries: int = 3
        +attempt: int
        +success: bool
        +removeFailedFile()
        +checkFileSize() bool
    }
    class FailedPageDetector {
        +sizeThreshold: int = 200
        +detectByContent(content) bool
        +detectBySize(size) bool
        +countFailed() int
    }
    class RecoverySystem {
        +retryFailed: bool
        +retryFailedPages()
        +createPlaceholder()
        +logError()
    }
    class WikiResult {
        +Failed: int
        +Status: string
        +Pages: WikiPageResult[]
    }
    class WikiPageResult {
        +Status: string
        +Size: int
    }
    ErrorHandler --> RetryMechanism : 使用
    RetryMechanism --> FailedPageDetector : 状態確認
    FailedPageDetector --> RecoverySystem : 失敗通知
    RecoverySystem --> WikiResult : 結果記録
    WikiResult --> WikiPageResult : 含む
Loading

Sources: main.go:96-112, main.go:462-470, main.go:563-636, main.go:741-875


Related Pages

Clone this wiki locally