fix: store version in gitignored .local_version to prevent notification spam (bd-tok)
Root cause: metadata.json is tracked in git and contains last_bd_version. When git operations (pull, checkout, merge) reset metadata.json to the committed version, the upgrade notification would fire repeatedly. Fix: Store the last used bd version in .beads/.local_version which is gitignored, so git operations don't affect version tracking. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
4
.beads/.gitignore
vendored
4
.beads/.gitignore
vendored
@@ -11,6 +11,10 @@ daemon.log
|
|||||||
daemon.pid
|
daemon.pid
|
||||||
bd.sock
|
bd.sock
|
||||||
|
|
||||||
|
# Local version tracking (prevents upgrade notification spam after git operations)
|
||||||
|
# bd-tok: Store version locally instead of in tracked metadata.json
|
||||||
|
.local_version
|
||||||
|
|
||||||
# Legacy database files
|
# Legacy database files
|
||||||
db.sqlite
|
db.sqlite
|
||||||
bd.db
|
bd.db
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/steveyegge/beads/internal/beads"
|
"github.com/steveyegge/beads/internal/beads"
|
||||||
@@ -12,11 +13,19 @@ import (
|
|||||||
"github.com/steveyegge/beads/internal/storage/sqlite"
|
"github.com/steveyegge/beads/internal/storage/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
// trackBdVersion checks if bd version has changed since last run and updates metadata.json.
|
// localVersionFile is the gitignored file that stores the last bd version used locally.
|
||||||
|
// This prevents the upgrade notification from firing repeatedly when git operations
|
||||||
|
// reset the tracked metadata.json file.
|
||||||
|
//
|
||||||
|
// bd-tok: Fix upgrade notification persisting after git operations
|
||||||
|
const localVersionFile = ".local_version"
|
||||||
|
|
||||||
|
// trackBdVersion checks if bd version has changed since last run and updates the local version file.
|
||||||
// This function is best-effort - failures are silent to avoid disrupting commands.
|
// This function is best-effort - failures are silent to avoid disrupting commands.
|
||||||
// Sets global variables versionUpgradeDetected and previousVersion if upgrade detected.
|
// Sets global variables versionUpgradeDetected and previousVersion if upgrade detected.
|
||||||
//
|
//
|
||||||
// bd-loka: Built-in version tracking for upgrade awareness
|
// bd-loka: Built-in version tracking for upgrade awareness
|
||||||
|
// bd-tok: Use gitignored .local_version file instead of metadata.json
|
||||||
func trackBdVersion() {
|
func trackBdVersion() {
|
||||||
// Find the beads directory
|
// Find the beads directory
|
||||||
beadsDir := beads.FindBeadsDir()
|
beadsDir := beads.FindBeadsDir()
|
||||||
@@ -25,16 +34,32 @@ func trackBdVersion() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load current config
|
// Read last version from local (gitignored) file
|
||||||
|
localVersionPath := filepath.Join(beadsDir, localVersionFile)
|
||||||
|
lastVersion := readLocalVersion(localVersionPath)
|
||||||
|
|
||||||
|
// Check if version changed
|
||||||
|
if lastVersion != "" && lastVersion != Version {
|
||||||
|
// Version upgrade detected!
|
||||||
|
versionUpgradeDetected = true
|
||||||
|
previousVersion = lastVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update local version file (best effort)
|
||||||
|
// Only write if version actually changed to minimize I/O
|
||||||
|
if lastVersion != Version {
|
||||||
|
_ = writeLocalVersion(localVersionPath, Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also ensure metadata.json exists with proper defaults (for JSONL export name)
|
||||||
|
// but don't use it for version tracking anymore
|
||||||
cfg, err := configfile.Load(beadsDir)
|
cfg, err := configfile.Load(beadsDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Silent failure - config might not exist yet
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
// No config file yet - create one with current version
|
// No config file yet - create one
|
||||||
cfg = configfile.DefaultConfig()
|
cfg = configfile.DefaultConfig()
|
||||||
cfg.LastBdVersion = Version
|
|
||||||
|
|
||||||
// bd-afd: Auto-detect actual JSONL file instead of using hardcoded default
|
// bd-afd: Auto-detect actual JSONL file instead of using hardcoded default
|
||||||
// This prevents mismatches when metadata.json gets deleted (git clean, merge conflict, etc.)
|
// This prevents mismatches when metadata.json gets deleted (git clean, merge conflict, etc.)
|
||||||
@@ -43,23 +68,23 @@ func trackBdVersion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ = cfg.Save(beadsDir) // Best effort
|
_ = cfg.Save(beadsDir) // Best effort
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if version changed
|
// readLocalVersion reads the last bd version from the local version file.
|
||||||
if cfg.LastBdVersion != "" && cfg.LastBdVersion != Version {
|
// Returns empty string if file doesn't exist or can't be read.
|
||||||
// Version upgrade detected!
|
func readLocalVersion(path string) string {
|
||||||
versionUpgradeDetected = true
|
// #nosec G304 - path is constructed from beadsDir + constant
|
||||||
previousVersion = cfg.LastBdVersion
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
return strings.TrimSpace(string(data))
|
||||||
|
}
|
||||||
|
|
||||||
// Update metadata.json with current version (best effort)
|
// writeLocalVersion writes the current version to the local version file.
|
||||||
// Only write if version actually changed to minimize I/O
|
func writeLocalVersion(path, version string) error {
|
||||||
// Also update on first run (when LastBdVersion is empty) to initialize tracking
|
return os.WriteFile(path, []byte(version+"\n"), 0600)
|
||||||
if cfg.LastBdVersion != Version {
|
|
||||||
cfg.LastBdVersion = Version
|
|
||||||
_ = cfg.Save(beadsDir) // Silent failure is fine
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getVersionsSince returns all version changes since the given version.
|
// getVersionsSince returns all version changes since the given version.
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/steveyegge/beads/internal/configfile"
|
|
||||||
"github.com/steveyegge/beads/internal/storage/sqlite"
|
"github.com/steveyegge/beads/internal/storage/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -156,7 +155,7 @@ func TestTrackBdVersion_FirstRun(t *testing.T) {
|
|||||||
versionUpgradeDetected = false
|
versionUpgradeDetected = false
|
||||||
previousVersion = ""
|
previousVersion = ""
|
||||||
|
|
||||||
// trackBdVersion should create metadata.json
|
// trackBdVersion should create .local_version
|
||||||
trackBdVersion()
|
trackBdVersion()
|
||||||
|
|
||||||
// Should not detect upgrade on first run
|
// Should not detect upgrade on first run
|
||||||
@@ -164,13 +163,11 @@ func TestTrackBdVersion_FirstRun(t *testing.T) {
|
|||||||
t.Error("Expected no upgrade detection on first run")
|
t.Error("Expected no upgrade detection on first run")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should have created metadata.json with current version
|
// Should have created .local_version with current version
|
||||||
cfg, err := configfile.Load(beadsDir)
|
localVersionPath := filepath.Join(beadsDir, localVersionFile)
|
||||||
if err != nil {
|
localVersion := readLocalVersion(localVersionPath)
|
||||||
t.Fatalf("Failed to load config after tracking: %v", err)
|
if localVersion != Version {
|
||||||
}
|
t.Errorf(".local_version = %q, want %q", localVersion, Version)
|
||||||
if cfg.LastBdVersion != Version {
|
|
||||||
t.Errorf("LastBdVersion = %q, want %q", cfg.LastBdVersion, Version)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,11 +186,16 @@ func TestTrackBdVersion_UpgradeDetection(t *testing.T) {
|
|||||||
t.Fatalf("Failed to change to temp dir: %v", err)
|
t.Fatalf("Failed to change to temp dir: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create metadata.json with old version
|
// Create minimal metadata.json so FindBeadsDir can find the directory (bd-420)
|
||||||
cfg := configfile.DefaultConfig()
|
metadataPath := filepath.Join(beadsDir, "metadata.json")
|
||||||
cfg.LastBdVersion = "0.22.0"
|
if err := os.WriteFile(metadataPath, []byte(`{"database":"beads.db"}`), 0600); err != nil {
|
||||||
if err := cfg.Save(beadsDir); err != nil {
|
t.Fatalf("Failed to create metadata.json: %v", err)
|
||||||
t.Fatalf("Failed to save config: %v", err)
|
}
|
||||||
|
|
||||||
|
// Create .local_version with old version (simulating previous bd run)
|
||||||
|
localVersionPath := filepath.Join(beadsDir, localVersionFile)
|
||||||
|
if err := writeLocalVersion(localVersionPath, "0.22.0"); err != nil {
|
||||||
|
t.Fatalf("Failed to write local version: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save original state
|
// Save original state
|
||||||
@@ -220,13 +222,10 @@ func TestTrackBdVersion_UpgradeDetection(t *testing.T) {
|
|||||||
t.Errorf("previousVersion = %q, want %q", previousVersion, "0.22.0")
|
t.Errorf("previousVersion = %q, want %q", previousVersion, "0.22.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should have updated metadata.json to current version
|
// Should have updated .local_version to current version
|
||||||
cfg, err := configfile.Load(beadsDir)
|
localVersion := readLocalVersion(localVersionPath)
|
||||||
if err != nil {
|
if localVersion != Version {
|
||||||
t.Fatalf("Failed to load config after tracking: %v", err)
|
t.Errorf(".local_version = %q, want %q", localVersion, Version)
|
||||||
}
|
|
||||||
if cfg.LastBdVersion != Version {
|
|
||||||
t.Errorf("LastBdVersion = %q, want %q", cfg.LastBdVersion, Version)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,11 +244,10 @@ func TestTrackBdVersion_SameVersion(t *testing.T) {
|
|||||||
t.Fatalf("Failed to change to temp dir: %v", err)
|
t.Fatalf("Failed to change to temp dir: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create metadata.json with current version
|
// Create .local_version with current version
|
||||||
cfg := configfile.DefaultConfig()
|
localVersionPath := filepath.Join(beadsDir, localVersionFile)
|
||||||
cfg.LastBdVersion = Version
|
if err := writeLocalVersion(localVersionPath, Version); err != nil {
|
||||||
if err := cfg.Save(beadsDir); err != nil {
|
t.Fatalf("Failed to write local version: %v", err)
|
||||||
t.Fatalf("Failed to save config: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save original state
|
// Save original state
|
||||||
|
|||||||
@@ -10,12 +10,17 @@ import (
|
|||||||
const ConfigFileName = "metadata.json"
|
const ConfigFileName = "metadata.json"
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Database string `json:"database"`
|
Database string `json:"database"`
|
||||||
JSONLExport string `json:"jsonl_export,omitempty"`
|
JSONLExport string `json:"jsonl_export,omitempty"`
|
||||||
LastBdVersion string `json:"last_bd_version,omitempty"`
|
|
||||||
|
|
||||||
// Deletions configuration
|
// Deletions configuration
|
||||||
DeletionsRetentionDays int `json:"deletions_retention_days,omitempty"` // 0 means use default (7 days)
|
DeletionsRetentionDays int `json:"deletions_retention_days,omitempty"` // 0 means use default (7 days)
|
||||||
|
|
||||||
|
// Deprecated: LastBdVersion is no longer used for version tracking.
|
||||||
|
// Version is now stored in .local_version (gitignored) to prevent
|
||||||
|
// upgrade notifications firing after git operations reset metadata.json.
|
||||||
|
// bd-tok: This field is kept for backwards compatibility when reading old configs.
|
||||||
|
LastBdVersion string `json:"last_bd_version,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultConfig() *Config {
|
func DefaultConfig() *Config {
|
||||||
|
|||||||
Reference in New Issue
Block a user