Files
beads/internal/export/executor.go
Steve Yegge e3e0a04496 Add configurable export error handling policies (bd-exug)
Implements flexible error handling for export operations with four policies:
- strict: Fail-fast on any error (default for user exports)
- best-effort: Skip errors with warnings (default for auto-exports)
- partial: Retry then skip with manifest tracking
- required-core: Fail on core data, skip enrichments

Key features:
- Per-project configuration via `bd config set export.error_policy`
- Separate policy for auto-exports: `auto_export.error_policy`
- Retry with exponential backoff (configurable attempts/delay)
- Optional export manifests documenting completeness
- Per-issue encoding error handling

This allows users to choose the right trade-off between data integrity
and system availability for their specific project needs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-23 20:21:51 -08:00

97 lines
2.6 KiB
Go

package export
import (
"context"
"fmt"
"os"
)
// DataType represents a type of data being fetched
type DataType string
const (
DataTypeCore DataType = "core" // Issues and dependencies
DataTypeLabels DataType = "labels" // Issue labels
DataTypeComments DataType = "comments" // Issue comments
)
// FetchResult holds the result of a data fetch operation
type FetchResult struct {
Success bool
Err error
Warnings []string
}
// FetchWithPolicy executes a fetch operation with the configured error policy
func FetchWithPolicy(ctx context.Context, cfg *Config, dataType DataType, desc string, fn func() error) FetchResult {
var result FetchResult
// Determine if this is core data
isCore := dataType == DataTypeCore
// Execute based on policy
switch cfg.Policy {
case PolicyStrict:
// Fail-fast on any error
err := RetryWithBackoff(ctx, cfg.RetryAttempts, cfg.RetryBackoffMS, desc, fn)
if err != nil {
result.Err = err
return result
}
result.Success = true
case PolicyBestEffort:
// Skip errors with warnings
err := RetryWithBackoff(ctx, cfg.RetryAttempts, cfg.RetryBackoffMS, desc, fn)
if err != nil {
warning := fmt.Sprintf("Warning: %s failed, skipping: %v", desc, err)
fmt.Fprintf(os.Stderr, "%s\n", warning)
result.Warnings = append(result.Warnings, warning)
result.Success = false // Data is missing
return result
}
result.Success = true
case PolicyPartial:
// Retry with backoff, then skip with manifest entry
err := RetryWithBackoff(ctx, cfg.RetryAttempts, cfg.RetryBackoffMS, desc, fn)
if err != nil {
warning := fmt.Sprintf("Warning: %s failed after retries, skipping: %v", desc, err)
fmt.Fprintf(os.Stderr, "%s\n", warning)
result.Warnings = append(result.Warnings, warning)
result.Success = false
return result
}
result.Success = true
case PolicyRequiredCore:
// Fail on core data, skip enrichments
if isCore {
err := RetryWithBackoff(ctx, cfg.RetryAttempts, cfg.RetryBackoffMS, desc, fn)
if err != nil {
result.Err = err
return result
}
result.Success = true
} else {
// Best-effort for enrichments
err := RetryWithBackoff(ctx, cfg.RetryAttempts, cfg.RetryBackoffMS, desc, fn)
if err != nil {
warning := fmt.Sprintf("Warning: %s (enrichment) failed, skipping: %v", desc, err)
fmt.Fprintf(os.Stderr, "%s\n", warning)
result.Warnings = append(result.Warnings, warning)
result.Success = false
return result
}
result.Success = true
}
default:
// Unknown policy, fail-fast as safest option
result.Err = fmt.Errorf("unknown error policy: %s", cfg.Policy)
return result
}
return result
}