-
Notifications
You must be signed in to change notification settings - Fork 1
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出力]
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" |
ページ生成に失敗した場合、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[プレースホルダー作成]
Sources: main.go:583-615
3回すべての試行が失敗した場合、以下の処理が実行される。
-
_errors.logへエラーを記録 - プレースホルダーファイルを作成(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 = failedCountSources: main.go:617-631
| 状態 | 条件 |
Status 値 |
|---|---|---|
| 成功 | ファイルが存在し、サイズ ≧ 200バイト | "ok" |
| 失敗 | ファイルが存在しない、またはサイズ < 200バイト | "failed" |
注意: 生成時の成功判定(100バイト超)と、集計時の失敗判定(200バイト未満)は異なる閾値を用いる。プレースホルダーファイルは約50バイト程度であるため、200バイト未満として失敗判定される。
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フラグを使用すると、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を参照のこと。
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: 完了
Sources: main.go:741-875
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の実行エラーは、コマンドのエラーと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[他プロジェクト処理継続]
# エラーログの確認
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フラグで再実行 |
# 失敗ページ数の確認
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 : 含む
Sources: main.go:96-112, main.go:462-470, main.go:563-636, main.go:741-875
- System Overview — wikigenのシステム全体像
- Architecture and Design — パイプライン構造とコンポーネント設計
-
CLI Reference and Usage —
-retryフラグを含む全CLIオプション - Repository Analysis Process — リポジトリ解析とページ生成フェーズ
- Parallelism and Performance — 並列処理とセマフォ制御
- JSON Output and Integration — JSON出力における失敗情報の活用
-
Output Format and File Structure —
_errors.logを含む出力ディレクトリ構造 - Authentication and Security — 入力バリデーションとセキュリティ対策
- System Overview
- Architecture & Design
- CLI Usage & Commands
- Configuration & Environment
- Input Formats & Repository Configuration
- Authentication & Git Integration
- Output Format & Wiki Structure
- Error Handling & Retry Mechanism
- Parallel Processing & Performance
- Input Validation & Security
- Build & Deployment
- Claude Code Integration
- Wiki Generation Processing Flow
- Multi-Repository Wiki Support
- Progress Tracking & Output Modes