fix(dolt): add YAML fallback for custom types/statuses

When the Dolt database connection is temporarily unavailable (e.g.,
stale connection, server restart), GetCustomTypes() and GetCustomStatuses()
now fall back to reading from config.yaml instead of failing.

This matches the SQLite storage behavior and fixes the stop hook
failure where `gt costs record` would fail with "invalid issue type: event"
when the Dolt connection was broken.

The fallback is checked in two cases:
1. When database query returns an error
2. When database has no custom types/statuses configured

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
joe
2026-01-24 20:25:48 -08:00
committed by gastown/crew/joe
parent bf0bf7b156
commit f845bb1be3
2 changed files with 82 additions and 8 deletions

View File

@@ -721,3 +721,30 @@ func GetCustomTypesFromYAML() []string {
}
return result
}
// GetCustomStatusesFromYAML retrieves custom statuses from config.yaml.
// This is used as a fallback when the database doesn't have status.custom set yet
// or when the database connection is temporarily unavailable.
// Returns nil if no custom statuses are configured in config.yaml.
func GetCustomStatusesFromYAML() []string {
if v == nil {
return nil
}
// Try to get status.custom from viper (config.yaml or env var)
value := v.GetString("status.custom")
if value == "" {
return nil
}
// Parse comma-separated list
parts := strings.Split(value, ",")
result := make([]string, 0, len(parts))
for _, p := range parts {
trimmed := strings.TrimSpace(p)
if trimmed != "" {
result = append(result, trimmed)
}
}
return result
}

View File

@@ -5,6 +5,8 @@ import (
"database/sql"
"fmt"
"strings"
"github.com/steveyegge/beads/internal/config"
)
// SetConfig sets a configuration value
@@ -85,26 +87,71 @@ func (s *DoltStore) GetMetadata(ctx context.Context, key string) (string, error)
return value, nil
}
// GetCustomStatuses returns custom status values from config
// GetCustomStatuses returns custom status values from config.
// If the database doesn't have custom statuses configured, falls back to config.yaml.
// Returns an empty slice if no custom statuses are configured.
func (s *DoltStore) GetCustomStatuses(ctx context.Context) ([]string, error) {
value, err := s.GetConfig(ctx, "status.custom")
if err != nil {
// On database error, try fallback to config.yaml
if yamlStatuses := config.GetCustomStatusesFromYAML(); len(yamlStatuses) > 0 {
return yamlStatuses, nil
}
return nil, err
}
if value == "" {
return nil, nil
if value != "" {
return parseCommaSeparatedList(value), nil
}
return strings.Split(value, ","), nil
// Fallback to config.yaml when database doesn't have status.custom set.
if yamlStatuses := config.GetCustomStatusesFromYAML(); len(yamlStatuses) > 0 {
return yamlStatuses, nil
}
return nil, nil
}
// GetCustomTypes returns custom issue type values from config
// GetCustomTypes returns custom issue type values from config.
// If the database doesn't have custom types configured, falls back to config.yaml.
// This fallback is essential during operations when the database connection is
// temporarily unavailable or when types.custom hasn't been configured yet.
// Returns an empty slice if no custom types are configured.
func (s *DoltStore) GetCustomTypes(ctx context.Context) ([]string, error) {
value, err := s.GetConfig(ctx, "types.custom")
if err != nil {
// On database error, try fallback to config.yaml
if yamlTypes := config.GetCustomTypesFromYAML(); len(yamlTypes) > 0 {
return yamlTypes, nil
}
return nil, err
}
if value == "" {
return nil, nil
if value != "" {
return parseCommaSeparatedList(value), nil
}
return strings.Split(value, ","), nil
// Fallback to config.yaml when database doesn't have types.custom set.
// This allows operations to work with custom types defined in config.yaml
// before they're persisted to the database.
if yamlTypes := config.GetCustomTypesFromYAML(); len(yamlTypes) > 0 {
return yamlTypes, nil
}
return nil, nil
}
// parseCommaSeparatedList splits a comma-separated string into a slice of trimmed entries.
// Empty entries are filtered out.
func parseCommaSeparatedList(value string) []string {
if value == "" {
return nil
}
parts := strings.Split(value, ",")
result := make([]string, 0, len(parts))
for _, p := range parts {
trimmed := strings.TrimSpace(p)
if trimmed != "" {
result = append(result, trimmed)
}
}
return result
}