fix(sync): use inline import for --import-only with redirect (bd-ysal)

When running `bd sync --import-only` from a directory with `.beads/redirect`,
the subprocess-based import could fail to update staleness metadata correctly
because the subprocess might resolve paths differently than the parent process.

The fix uses inline import (calling importIssuesCore directly) instead of
spawning a subprocess. This ensures:
1. The same store and dbPath are used throughout
2. Path resolution is consistent with the parent process
3. Staleness metadata is updated correctly in the redirected database

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beads/crew/wolf
2026-01-06 18:51:38 -08:00
committed by Steve Yegge
parent cca2016376
commit 94997bd619
2 changed files with 93 additions and 1 deletions

View File

@@ -110,12 +110,13 @@ Use --merge to merge the sync branch back to main branch.`,
}
// If import-only mode, just import and exit
// Use inline import to avoid subprocess path resolution issues with .beads/redirect (bd-ysal)
if importOnly {
if dryRun {
fmt.Println("→ [DRY RUN] Would import from JSONL")
} else {
fmt.Println("→ Importing from JSONL...")
if err := importFromJSONL(ctx, jsonlPath, renameOnImport, noGitHistory); err != nil {
if err := importFromJSONLInline(ctx, jsonlPath, renameOnImport, noGitHistory); err != nil {
FatalError("importing: %v", err)
}
fmt.Println("✓ Import complete")

View File

@@ -1,10 +1,16 @@
package main
import (
"bufio"
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"time"
"github.com/steveyegge/beads/internal/debug"
"github.com/steveyegge/beads/internal/types"
)
// importFromJSONL imports the JSONL file by running the import command
@@ -55,6 +61,91 @@ func importFromJSONL(ctx context.Context, jsonlPath string, renameOnImport bool,
return nil
}
// importFromJSONLInline imports the JSONL file directly without spawning a subprocess.
// This avoids path resolution issues when running from directories with .beads/redirect.
// The parent process's store and dbPath are used, ensuring consistent path resolution.
// (bd-ysal fix)
func importFromJSONLInline(ctx context.Context, jsonlPath string, renameOnImport bool, noGitHistory bool) error {
// Verify we have an active store
if store == nil {
return fmt.Errorf("no database store available for inline import")
}
// Read and parse the JSONL file
// #nosec G304 - jsonlPath is from findJSONLPath() which uses trusted paths
f, err := os.Open(jsonlPath)
if err != nil {
return fmt.Errorf("failed to open JSONL file: %w", err)
}
defer func() { _ = f.Close() }()
var allIssues []*types.Issue
scanner := bufio.NewScanner(f)
lineNum := 0
for scanner.Scan() {
lineNum++
line := scanner.Text()
if line == "" {
continue
}
var issue types.Issue
if err := json.Unmarshal([]byte(line), &issue); err != nil {
return fmt.Errorf("error parsing line %d: %w", lineNum, err)
}
issue.SetDefaults()
allIssues = append(allIssues, &issue)
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("error reading JSONL: %w", err)
}
// Import using shared logic
opts := ImportOptions{
RenameOnImport: renameOnImport,
}
result, err := importIssuesCore(ctx, dbPath, store, allIssues, opts)
if err != nil {
return fmt.Errorf("import failed: %w", err)
}
// Update staleness metadata (same as import.go lines 386-411)
// This is critical: without this, CheckStaleness will still report stale
if currentHash, hashErr := computeJSONLHash(jsonlPath); hashErr == nil {
if err := store.SetMetadata(ctx, "jsonl_content_hash", currentHash); err != nil {
debug.Logf("Warning: failed to update jsonl_content_hash: %v", err)
}
if err := store.SetJSONLFileHash(ctx, currentHash); err != nil {
debug.Logf("Warning: failed to update jsonl_file_hash: %v", err)
}
importTime := time.Now().Format(time.RFC3339Nano)
if err := store.SetMetadata(ctx, "last_import_time", importTime); err != nil {
debug.Logf("Warning: failed to update last_import_time: %v", err)
}
} else {
debug.Logf("Warning: failed to compute JSONL hash: %v", hashErr)
}
// Update database mtime
if err := TouchDatabaseFile(dbPath, jsonlPath); err != nil {
debug.Logf("Warning: failed to update database mtime: %v", err)
}
// Print summary
fmt.Fprintf(os.Stderr, "Import complete: %d created, %d updated", result.Created, result.Updated)
if result.Unchanged > 0 {
fmt.Fprintf(os.Stderr, ", %d unchanged", result.Unchanged)
}
if result.Skipped > 0 {
fmt.Fprintf(os.Stderr, ", %d skipped", result.Skipped)
}
fmt.Fprintf(os.Stderr, "\n")
return nil
}
// resolveNoGitHistoryForFromMain returns the resolved noGitHistory value for sync operations.
// When syncing from main (--from-main), noGitHistory is forced to true to prevent creating
// incorrect deletion records for locally-created beads that don't exist on main.