Fix bd-166: Prevent duplicate issues with wrong prefix

Critical fix for silent data corruption where database created 173
duplicate issues with wrong prefix (beads- instead of bd-).

Root cause: When issue_prefix config was missing, CreateIssue fell
back to deriving prefix from database filename (beads.db → 'beads'),
while auto-import imported bd- issues from git with SkipPrefixValidation.
This created duplicates.

Changes:
1. Removed derivePrefixFromPath() - never derive prefix from filename
2. CreateIssue/CreateIssues now REJECT if issue_prefix config missing
   - Fail-fast with clear error message
3. Auto-import now SETS issue_prefix from first imported issue if missing
   - Handles fresh clone scenario safely
4. Added newTestStore() helper that sets issue_prefix for tests
5. Updated test setup in multiple files to prevent test failures

Follow-ups filed: bd-167, bd-168, bd-169

Closes bd-166

Amp-Thread-ID: https://ampcode.com/threads/T-b2ee0738-b90b-40ef-ae44-f2d93729842c
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Steve Yegge
2025-10-26 21:55:01 -07:00
parent 669c054332
commit 09e881022e
11 changed files with 108 additions and 40 deletions

View File

@@ -9,6 +9,7 @@ import (
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/steveyegge/beads/internal/storage"
"github.com/steveyegge/beads/internal/types"
@@ -167,7 +168,24 @@ func importFromGit(ctx context.Context, dbFilePath string, store storage.Storage
return fmt.Errorf("failed to scan JSONL: %w", err)
}
// CRITICAL (bd-166): Set issue_prefix from first imported issue if missing
// This prevents derivePrefixFromPath fallback which caused duplicate issues
if len(issues) > 0 {
configuredPrefix, err := store.GetConfig(ctx, "issue_prefix")
if err == nil && strings.TrimSpace(configuredPrefix) == "" {
// Database has no prefix configured - derive from first issue
firstPrefix := extractPrefix(issues[0].ID)
if firstPrefix != "" {
if err := store.SetConfig(ctx, "issue_prefix", firstPrefix); err != nil {
return fmt.Errorf("failed to set issue_prefix from imported issues: %w", err)
}
}
}
}
// Use existing import logic with auto-resolve collisions
// Note: SkipPrefixValidation allows mixed prefixes during auto-import
// (but now we set the prefix first, so CreateIssue won't use filename fallback)
opts := ImportOptions{
ResolveCollisions: true,
DryRun: false,