From aa3b318939b4b7fa236888094842b64e9c57c5f5 Mon Sep 17 00:00:00 2001 From: Bobby Johnson Date: Mon, 19 Jan 2026 10:11:19 -0800 Subject: [PATCH] Fix issue-prefix config fallback to config.yaml (GH#1145) (#1146) The config.yaml file uses "issue-prefix" (with hyphen) but this setting was only read during bd init. After initialization, all code read from the database's "issue_prefix" key, so subsequent changes to config.yaml were silently ignored. This fix adds a fallback to config.yaml's "issue-prefix" in the key locations where the prefix is retrieved from the database: 1. autoflush.go: Auto-import now checks config.yaml before falling back to auto-detection from JSONL or directory name 2. autoflush.go: filterByMultiRepoPrefix now checks config.yaml as fallback 3. create.go: Direct mode prefix validation now checks config.yaml as fallback Priority order is preserved: 1. Database issue_prefix (if set) 2. Config.yaml issue-prefix (new fallback) 3. Auto-detection from JSONL/directory (existing fallback) Fixes #1145 Co-authored-by: Claude Opus 4.5 --- cmd/bd/autoflush.go | 39 +++++++++++++++++++++++---------------- cmd/bd/create.go | 5 ++++- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/cmd/bd/autoflush.go b/cmd/bd/autoflush.go index 95701d23..d0ad516e 100644 --- a/cmd/bd/autoflush.go +++ b/cmd/bd/autoflush.go @@ -253,8 +253,14 @@ func autoImportIfNewer() { if store != nil { prefix, prefixErr := store.GetConfig(ctx, "issue_prefix") if prefixErr != nil || prefix == "" { - // Database needs initialization - detect prefix from JSONL or directory - detectedPrefix := detectPrefixFromJSONL(jsonlData) + // GH#1145: Check config.yaml for issue-prefix before auto-detecting + detectedPrefix := config.GetString("issue-prefix") + + // If config.yaml doesn't have it, try to detect from JSONL + if detectedPrefix == "" { + detectedPrefix = detectPrefixFromJSONL(jsonlData) + } + if detectedPrefix == "" { // Fallback: detect from directory name beadsDir := filepath.Dir(jsonlPath) @@ -338,7 +344,7 @@ func autoImportIfNewer() { if err := store.ClearAllExportHashes(ctx); err != nil { fmt.Fprintf(os.Stderr, "Warning: failed to clear export_hashes before import: %v\n", err) } - + // Use shared import logic opts := ImportOptions{ DryRun: false, @@ -418,8 +424,6 @@ func autoImportIfNewer() { } } - - // markDirtyAndScheduleFlush marks the database as dirty and schedules a flush // markDirtyAndScheduleFlush marks the database as dirty and schedules a debounced // export to JSONL. Uses FlushManager's event-driven architecture. @@ -485,12 +489,12 @@ func validateJSONLIntegrity(ctx context.Context, jsonlPath string) (bool, error) if err != nil { return false, fmt.Errorf("failed to get stored JSONL hash: %w", err) } - + // If no hash stored, this is first export - skip validation if storedHash == "" { return false, nil } - + // Read current JSONL file jsonlData, err := os.ReadFile(jsonlPath) if err != nil { @@ -508,12 +512,12 @@ func validateJSONLIntegrity(ctx context.Context, jsonlPath string) (bool, error) } return false, fmt.Errorf("failed to read JSONL file: %w", err) } - + // Compute current JSONL hash hasher := sha256.New() hasher.Write(jsonlData) currentHash := hex.EncodeToString(hasher.Sum(nil)) - + // Compare hashes if currentHash != storedHash { fmt.Fprintf(os.Stderr, "⚠️ WARNING: JSONL file hash mismatch detected\n") @@ -530,7 +534,7 @@ func validateJSONLIntegrity(ctx context.Context, jsonlPath string) (bool, error) } return true, nil // Signal full export needed } - + return false, nil } @@ -559,15 +563,15 @@ func writeJSONLAtomic(jsonlPath string, issues []*types.Issue) ([]string, error) encoder := json.NewEncoder(f) skippedCount := 0 exportedIDs := make([]string, 0, len(issues)) - + for _, issue := range issues { if err := encoder.Encode(issue); err != nil { - return nil, fmt.Errorf("failed to encode issue %s: %w", issue.ID, err) + return nil, fmt.Errorf("failed to encode issue %s: %w", issue.ID, err) } - + exportedIDs = append(exportedIDs, issue.ID) } - + // Report skipped issues if any (helps debugging) if skippedCount > 0 { debug.Logf("auto-flush skipped %d issue(s) with timestamp-only changes", skippedCount) @@ -726,10 +730,13 @@ func filterByMultiRepoPrefix(ctx context.Context, s storage.Storage, issues []*t return issues } - // Get our configured prefix + // Get our configured prefix (GH#1145: fallback to config.yaml) prefix, prefixErr := s.GetConfig(ctx, "issue_prefix") if prefixErr != nil || prefix == "" { - return issues + prefix = config.GetString("issue-prefix") + if prefix == "" { + return issues + } } // Determine if we're the primary repo diff --git a/cmd/bd/create.go b/cmd/bd/create.go index 295df5a7..545c0b7a 100644 --- a/cmd/bd/create.go +++ b/cmd/bd/create.go @@ -432,8 +432,11 @@ var createCmd = &cobra.Command{ } // If error, continue without validation (non-fatal) } else { - // Direct mode - check config + // Direct mode - check config (GH#1145: fallback to config.yaml) dbPrefix, _ = store.GetConfig(ctx, "issue_prefix") + if dbPrefix == "" { + dbPrefix = config.GetString("issue-prefix") + } allowedPrefixes, _ = store.GetConfig(ctx, "allowed_prefixes") }