fix(create): allow creating issues with explicit ID that matches tombstone (bd-0gm4r)
When using `bd create --id=<id>` where the ID matches an existing tombstone (from `bd delete --hard --force`), the creation now succeeds by first deleting the tombstone and all related records. This enables use cases like polecat respawn where a worker needs to recreate an issue with the same ID. Changes: - queries.go: Check for tombstone before insert, delete it if found (cleans up events, labels, dependencies, comments, dirty_issues) - tombstone_test.go: Add regression test Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Executed-By: beads/crew/dave Rig: beads Role: crew
This commit is contained in:
committed by
Steve Yegge
parent
3298c45e4b
commit
9e639da5ba
@@ -216,6 +216,40 @@ func (s *SQLiteStorage) CreateIssue(ctx context.Context, issue *types.Issue, act
|
||||
}
|
||||
}
|
||||
|
||||
// bd-0gm4r: Handle tombstone collision for explicit IDs
|
||||
// If the user explicitly specifies an ID that matches an existing tombstone,
|
||||
// delete the tombstone first so the new issue can be created.
|
||||
// This enables re-creating issues after hard deletion (e.g., polecat respawn).
|
||||
if issue.ID != "" {
|
||||
var existingStatus string
|
||||
err := conn.QueryRowContext(ctx, `SELECT status FROM issues WHERE id = ?`, issue.ID).Scan(&existingStatus)
|
||||
if err == nil && existingStatus == string(types.StatusTombstone) {
|
||||
// Delete the tombstone record to allow re-creation
|
||||
// Also clean up related tables (events, labels, dependencies, comments, dirty_issues)
|
||||
if _, err := conn.ExecContext(ctx, `DELETE FROM events WHERE issue_id = ?`, issue.ID); err != nil {
|
||||
return fmt.Errorf("failed to delete tombstone events: %w", err)
|
||||
}
|
||||
if _, err := conn.ExecContext(ctx, `DELETE FROM labels WHERE issue_id = ?`, issue.ID); err != nil {
|
||||
return fmt.Errorf("failed to delete tombstone labels: %w", err)
|
||||
}
|
||||
if _, err := conn.ExecContext(ctx, `DELETE FROM dependencies WHERE issue_id = ? OR depends_on_id = ?`, issue.ID, issue.ID); err != nil {
|
||||
return fmt.Errorf("failed to delete tombstone dependencies: %w", err)
|
||||
}
|
||||
if _, err := conn.ExecContext(ctx, `DELETE FROM comments WHERE issue_id = ?`, issue.ID); err != nil {
|
||||
return fmt.Errorf("failed to delete tombstone comments: %w", err)
|
||||
}
|
||||
if _, err := conn.ExecContext(ctx, `DELETE FROM dirty_issues WHERE issue_id = ?`, issue.ID); err != nil {
|
||||
return fmt.Errorf("failed to delete tombstone dirty marker: %w", err)
|
||||
}
|
||||
if _, err := conn.ExecContext(ctx, `DELETE FROM issues WHERE id = ?`, issue.ID); err != nil {
|
||||
return fmt.Errorf("failed to delete tombstone: %w", err)
|
||||
}
|
||||
// Note: Tombstone is now gone, proceed with normal creation
|
||||
} else if err != nil && err != sql.ErrNoRows {
|
||||
return fmt.Errorf("failed to check for existing tombstone: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Insert issue using strict mode (fails on duplicates)
|
||||
// GH#956: Use insertIssueStrict instead of insertIssue to prevent FK constraint errors
|
||||
// from silent INSERT OR IGNORE failures under concurrent load.
|
||||
|
||||
Reference in New Issue
Block a user