fix(db): add close_reason column to issues table (bd-uyu)

- Add migration 017_close_reason_column.go to create the column
- Update all INSERT statements to include close_reason
- Update all SELECT statements to include close_reason
- Update doctor.go to check for close_reason in schema validation
- Remove workaround code that batch-loaded close reasons from events table
- Fix migrations_test.go to include close_reason in test table schema

This fixes sync loops where close_reason values were silently dropped
because the DB lacked the column despite the struct having the field.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-01 21:56:41 -08:00
parent d880fceb0f
commit 09fffa4eaf
11 changed files with 79 additions and 42 deletions

View File

@@ -1780,7 +1780,7 @@ func checkSchemaCompatibility(path string) doctorCheck {
// This is a simplified version since we can't import the internal package directly // This is a simplified version since we can't import the internal package directly
// Check all critical tables and columns // Check all critical tables and columns
criticalChecks := map[string][]string{ criticalChecks := map[string][]string{
"issues": {"id", "title", "content_hash", "external_ref", "compacted_at"}, "issues": {"id", "title", "content_hash", "external_ref", "compacted_at", "close_reason"},
"dependencies": {"issue_id", "depends_on_id", "type"}, "dependencies": {"issue_id", "depends_on_id", "type"},
"child_counters": {"parent_id", "last_child"}, "child_counters": {"parent_id", "last_child"},
"export_hashes": {"issue_id", "content_hash"}, "export_hashes": {"issue_id", "content_hash"},

View File

@@ -678,7 +678,7 @@ func (s *SQLiteStorage) DetectCycles(ctx context.Context) ([][]*types.Issue, err
func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*types.Issue, error) { func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*types.Issue, error) {
var issues []*types.Issue var issues []*types.Issue
var issueIDs []string var issueIDs []string
// First pass: scan all issues // First pass: scan all issues
for rows.Next() { for rows.Next() {
var issue types.Issue var issue types.Issue
@@ -688,12 +688,13 @@ func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*type
var assignee sql.NullString var assignee sql.NullString
var externalRef sql.NullString var externalRef sql.NullString
var sourceRepo sql.NullString var sourceRepo sql.NullString
var closeReason sql.NullString
err := rows.Scan( err := rows.Scan(
&issue.ID, &contentHash, &issue.Title, &issue.Description, &issue.Design, &issue.ID, &contentHash, &issue.Title, &issue.Description, &issue.Design,
&issue.AcceptanceCriteria, &issue.Notes, &issue.Status, &issue.AcceptanceCriteria, &issue.Notes, &issue.Status,
&issue.Priority, &issue.IssueType, &assignee, &estimatedMinutes, &issue.Priority, &issue.IssueType, &assignee, &estimatedMinutes,
&issue.CreatedAt, &issue.UpdatedAt, &closedAt, &externalRef, &sourceRepo, &issue.CreatedAt, &issue.UpdatedAt, &closedAt, &externalRef, &sourceRepo, &closeReason,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to scan issue: %w", err) return nil, fmt.Errorf("failed to scan issue: %w", err)
@@ -718,6 +719,9 @@ func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*type
if sourceRepo.Valid { if sourceRepo.Valid {
issue.SourceRepo = sourceRepo.String issue.SourceRepo = sourceRepo.String
} }
if closeReason.Valid {
issue.CloseReason = closeReason.String
}
issues = append(issues, &issue) issues = append(issues, &issue)
issueIDs = append(issueIDs, issue.ID) issueIDs = append(issueIDs, issue.ID)
@@ -736,19 +740,6 @@ func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*type
} }
} }
// Third pass: batch-load close reasons for closed issues
closeReasonsMap, err := s.GetCloseReasonsForIssues(ctx, issueIDs)
if err != nil {
return nil, fmt.Errorf("failed to batch get close reasons: %w", err)
}
// Assign close reasons to issues
for _, issue := range issues {
if reason, ok := closeReasonsMap[issue.ID]; ok {
issue.CloseReason = reason
}
}
return issues, nil return issues, nil
} }

View File

@@ -14,19 +14,19 @@ func insertIssue(ctx context.Context, conn *sql.Conn, issue *types.Issue) error
if sourceRepo == "" { if sourceRepo == "" {
sourceRepo = "." // Default to primary repo sourceRepo = "." // Default to primary repo
} }
_, err := conn.ExecContext(ctx, ` _, err := conn.ExecContext(ctx, `
INSERT INTO issues ( INSERT INTO issues (
id, content_hash, title, description, design, acceptance_criteria, notes, id, content_hash, title, description, design, acceptance_criteria, notes,
status, priority, issue_type, assignee, estimated_minutes, status, priority, issue_type, assignee, estimated_minutes,
created_at, updated_at, closed_at, external_ref, source_repo created_at, updated_at, closed_at, external_ref, source_repo, close_reason
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, `,
issue.ID, issue.ContentHash, issue.Title, issue.Description, issue.Design, issue.ID, issue.ContentHash, issue.Title, issue.Description, issue.Design,
issue.AcceptanceCriteria, issue.Notes, issue.Status, issue.AcceptanceCriteria, issue.Notes, issue.Status,
issue.Priority, issue.IssueType, issue.Assignee, issue.Priority, issue.IssueType, issue.Assignee,
issue.EstimatedMinutes, issue.CreatedAt, issue.UpdatedAt, issue.EstimatedMinutes, issue.CreatedAt, issue.UpdatedAt,
issue.ClosedAt, issue.ExternalRef, sourceRepo, issue.ClosedAt, issue.ExternalRef, sourceRepo, issue.CloseReason,
) )
if err != nil { if err != nil {
return fmt.Errorf("failed to insert issue: %w", err) return fmt.Errorf("failed to insert issue: %w", err)
@@ -40,8 +40,8 @@ func insertIssues(ctx context.Context, conn *sql.Conn, issues []*types.Issue) er
INSERT INTO issues ( INSERT INTO issues (
id, content_hash, title, description, design, acceptance_criteria, notes, id, content_hash, title, description, design, acceptance_criteria, notes,
status, priority, issue_type, assignee, estimated_minutes, status, priority, issue_type, assignee, estimated_minutes,
created_at, updated_at, closed_at, external_ref, source_repo created_at, updated_at, closed_at, external_ref, source_repo, close_reason
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`) `)
if err != nil { if err != nil {
return fmt.Errorf("failed to prepare statement: %w", err) return fmt.Errorf("failed to prepare statement: %w", err)
@@ -53,13 +53,13 @@ func insertIssues(ctx context.Context, conn *sql.Conn, issues []*types.Issue) er
if sourceRepo == "" { if sourceRepo == "" {
sourceRepo = "." // Default to primary repo sourceRepo = "." // Default to primary repo
} }
_, err = stmt.ExecContext(ctx, _, err = stmt.ExecContext(ctx,
issue.ID, issue.ContentHash, issue.Title, issue.Description, issue.Design, issue.ID, issue.ContentHash, issue.Title, issue.Description, issue.Design,
issue.AcceptanceCriteria, issue.Notes, issue.Status, issue.AcceptanceCriteria, issue.Notes, issue.Status,
issue.Priority, issue.IssueType, issue.Assignee, issue.Priority, issue.IssueType, issue.Assignee,
issue.EstimatedMinutes, issue.CreatedAt, issue.UpdatedAt, issue.EstimatedMinutes, issue.CreatedAt, issue.UpdatedAt,
issue.ClosedAt, issue.ExternalRef, sourceRepo, issue.ClosedAt, issue.ExternalRef, sourceRepo, issue.CloseReason,
) )
if err != nil { if err != nil {
return fmt.Errorf("failed to insert issue %s: %w", issue.ID, err) return fmt.Errorf("failed to insert issue %s: %w", issue.ID, err)

View File

@@ -157,7 +157,7 @@ func (s *SQLiteStorage) GetIssuesByLabel(ctx context.Context, label string) ([]*
rows, err := s.db.QueryContext(ctx, ` rows, err := s.db.QueryContext(ctx, `
SELECT i.id, i.content_hash, i.title, i.description, i.design, i.acceptance_criteria, i.notes, SELECT i.id, i.content_hash, i.title, i.description, i.design, i.acceptance_criteria, i.notes,
i.status, i.priority, i.issue_type, i.assignee, i.estimated_minutes, i.status, i.priority, i.issue_type, i.assignee, i.estimated_minutes,
i.created_at, i.updated_at, i.closed_at, i.external_ref, i.source_repo i.created_at, i.updated_at, i.closed_at, i.external_ref, i.source_repo, i.close_reason
FROM issues i FROM issues i
JOIN labels l ON i.id = l.issue_id JOIN labels l ON i.id = l.issue_id
WHERE l.label = ? WHERE l.label = ?

View File

@@ -33,6 +33,7 @@ var migrationsList = []Migration{
{"child_counters_table", migrations.MigrateChildCountersTable}, {"child_counters_table", migrations.MigrateChildCountersTable},
{"blocked_issues_cache", migrations.MigrateBlockedIssuesCache}, {"blocked_issues_cache", migrations.MigrateBlockedIssuesCache},
{"orphan_detection", migrations.MigrateOrphanDetection}, {"orphan_detection", migrations.MigrateOrphanDetection},
{"close_reason_column", migrations.MigrateCloseReasonColumn},
} }
// MigrationInfo contains metadata about a migration for inspection // MigrationInfo contains metadata about a migration for inspection
@@ -73,6 +74,7 @@ func getMigrationDescription(name string) string {
"child_counters_table": "Adds child_counters table for hierarchical ID generation with ON DELETE CASCADE", "child_counters_table": "Adds child_counters table for hierarchical ID generation with ON DELETE CASCADE",
"blocked_issues_cache": "Adds blocked_issues_cache table for GetReadyWork performance optimization (bd-5qim)", "blocked_issues_cache": "Adds blocked_issues_cache table for GetReadyWork performance optimization (bd-5qim)",
"orphan_detection": "Detects orphaned child issues and logs them for user action (bd-3852)", "orphan_detection": "Detects orphaned child issues and logs them for user action (bd-3852)",
"close_reason_column": "Adds close_reason column to issues table for storing closure explanations (bd-uyu)",
} }
if desc, ok := descriptions[name]; ok { if desc, ok := descriptions[name]; ok {

View File

@@ -0,0 +1,31 @@
package migrations
import (
"database/sql"
"fmt"
)
// MigrateCloseReasonColumn adds the close_reason column to the issues table.
// This column stores the reason provided when closing an issue.
func MigrateCloseReasonColumn(db *sql.DB) error {
var columnExists bool
err := db.QueryRow(`
SELECT COUNT(*) > 0
FROM pragma_table_info('issues')
WHERE name = 'close_reason'
`).Scan(&columnExists)
if err != nil {
return fmt.Errorf("failed to check close_reason column: %w", err)
}
if columnExists {
return nil
}
_, err = db.Exec(`ALTER TABLE issues ADD COLUMN close_reason TEXT DEFAULT ''`)
if err != nil {
return fmt.Errorf("failed to add close_reason column: %w", err)
}
return nil
}

View File

@@ -478,9 +478,10 @@ func TestMigrateContentHashColumn(t *testing.T) {
original_size INTEGER, original_size INTEGER,
compacted_at_commit TEXT, compacted_at_commit TEXT,
source_repo TEXT DEFAULT '.', source_repo TEXT DEFAULT '.',
close_reason TEXT DEFAULT '',
CHECK ((status = 'closed') = (closed_at IS NOT NULL)) CHECK ((status = 'closed') = (closed_at IS NOT NULL))
); );
INSERT INTO issues SELECT id, title, description, design, acceptance_criteria, notes, status, priority, issue_type, assignee, estimated_minutes, created_at, updated_at, closed_at, external_ref, compaction_level, compacted_at, original_size, compacted_at_commit, source_repo FROM issues_backup; INSERT INTO issues SELECT id, title, description, design, acceptance_criteria, notes, status, priority, issue_type, assignee, estimated_minutes, created_at, updated_at, closed_at, external_ref, compaction_level, compacted_at, original_size, compacted_at_commit, source_repo, '' FROM issues_backup;
DROP TABLE issues_backup; DROP TABLE issues_backup;
`) `)
if err != nil { if err != nil {

View File

@@ -241,14 +241,14 @@ func (s *SQLiteStorage) upsertIssueInTx(ctx context.Context, tx *sql.Tx, issue *
INSERT INTO issues ( INSERT INTO issues (
id, content_hash, title, description, design, acceptance_criteria, notes, id, content_hash, title, description, design, acceptance_criteria, notes,
status, priority, issue_type, assignee, estimated_minutes, status, priority, issue_type, assignee, estimated_minutes,
created_at, updated_at, closed_at, external_ref, source_repo created_at, updated_at, closed_at, external_ref, source_repo, close_reason
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, `,
issue.ID, issue.ContentHash, issue.Title, issue.Description, issue.Design, issue.ID, issue.ContentHash, issue.Title, issue.Description, issue.Design,
issue.AcceptanceCriteria, issue.Notes, issue.Status, issue.AcceptanceCriteria, issue.Notes, issue.Status,
issue.Priority, issue.IssueType, issue.Assignee, issue.Priority, issue.IssueType, issue.Assignee,
issue.EstimatedMinutes, issue.CreatedAt, issue.UpdatedAt, issue.EstimatedMinutes, issue.CreatedAt, issue.UpdatedAt,
issue.ClosedAt, issue.ExternalRef, issue.SourceRepo, issue.ClosedAt, issue.ExternalRef, issue.SourceRepo, issue.CloseReason,
) )
if err != nil { if err != nil {
return fmt.Errorf("failed to insert issue: %w", err) return fmt.Errorf("failed to insert issue: %w", err)

View File

@@ -157,6 +157,7 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue,
var compactedAt sql.NullTime var compactedAt sql.NullTime
var originalSize sql.NullInt64 var originalSize sql.NullInt64
var sourceRepo sql.NullString var sourceRepo sql.NullString
var closeReason sql.NullString
var contentHash sql.NullString var contentHash sql.NullString
var compactedAtCommit sql.NullString var compactedAtCommit sql.NullString
@@ -164,7 +165,7 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue,
SELECT id, content_hash, title, description, design, acceptance_criteria, notes, SELECT id, content_hash, title, description, design, acceptance_criteria, notes,
status, priority, issue_type, assignee, estimated_minutes, status, priority, issue_type, assignee, estimated_minutes,
created_at, updated_at, closed_at, external_ref, created_at, updated_at, closed_at, external_ref,
compaction_level, compacted_at, compacted_at_commit, original_size, source_repo compaction_level, compacted_at, compacted_at_commit, original_size, source_repo, close_reason
FROM issues FROM issues
WHERE id = ? WHERE id = ?
`, id).Scan( `, id).Scan(
@@ -172,7 +173,7 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue,
&issue.AcceptanceCriteria, &issue.Notes, &issue.Status, &issue.AcceptanceCriteria, &issue.Notes, &issue.Status,
&issue.Priority, &issue.IssueType, &assignee, &estimatedMinutes, &issue.Priority, &issue.IssueType, &assignee, &estimatedMinutes,
&issue.CreatedAt, &issue.UpdatedAt, &closedAt, &externalRef, &issue.CreatedAt, &issue.UpdatedAt, &closedAt, &externalRef,
&issue.CompactionLevel, &compactedAt, &compactedAtCommit, &originalSize, &sourceRepo, &issue.CompactionLevel, &compactedAt, &compactedAtCommit, &originalSize, &sourceRepo, &closeReason,
) )
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
@@ -210,6 +211,9 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue,
if sourceRepo.Valid { if sourceRepo.Valid {
issue.SourceRepo = sourceRepo.String issue.SourceRepo = sourceRepo.String
} }
if closeReason.Valid {
issue.CloseReason = closeReason.String
}
// Fetch labels for this issue // Fetch labels for this issue
labels, err := s.GetLabels(ctx, issue.ID) labels, err := s.GetLabels(ctx, issue.ID)
@@ -1263,7 +1267,7 @@ func (s *SQLiteStorage) SearchIssues(ctx context.Context, query string, filter t
querySQL := fmt.Sprintf(` querySQL := fmt.Sprintf(`
SELECT id, content_hash, title, description, design, acceptance_criteria, notes, SELECT id, content_hash, title, description, design, acceptance_criteria, notes,
status, priority, issue_type, assignee, estimated_minutes, status, priority, issue_type, assignee, estimated_minutes,
created_at, updated_at, closed_at, external_ref, source_repo created_at, updated_at, closed_at, external_ref, source_repo, close_reason
FROM issues FROM issues
%s %s
ORDER BY priority ASC, created_at DESC ORDER BY priority ASC, created_at DESC

View File

@@ -99,7 +99,7 @@ func (s *SQLiteStorage) GetReadyWork(ctx context.Context, filter types.WorkFilte
query := fmt.Sprintf(` query := fmt.Sprintf(`
SELECT i.id, i.content_hash, i.title, i.description, i.design, i.acceptance_criteria, i.notes, SELECT i.id, i.content_hash, i.title, i.description, i.design, i.acceptance_criteria, i.notes,
i.status, i.priority, i.issue_type, i.assignee, i.estimated_minutes, i.status, i.priority, i.issue_type, i.assignee, i.estimated_minutes,
i.created_at, i.updated_at, i.closed_at, i.external_ref, i.source_repo i.created_at, i.updated_at, i.closed_at, i.external_ref, i.source_repo, i.close_reason
FROM issues i FROM issues i
WHERE %s WHERE %s
AND NOT EXISTS ( AND NOT EXISTS (
@@ -126,7 +126,7 @@ func (s *SQLiteStorage) GetStaleIssues(ctx context.Context, filter types.StaleFi
id, content_hash, title, description, design, acceptance_criteria, notes, id, content_hash, title, description, design, acceptance_criteria, notes,
status, priority, issue_type, assignee, estimated_minutes, status, priority, issue_type, assignee, estimated_minutes,
created_at, updated_at, closed_at, external_ref, source_repo, created_at, updated_at, closed_at, external_ref, source_repo,
compaction_level, compacted_at, compacted_at_commit, original_size compaction_level, compacted_at, compacted_at_commit, original_size, close_reason
FROM issues FROM issues
WHERE status != 'closed' WHERE status != 'closed'
AND datetime(updated_at) < datetime('now', '-' || ? || ' days') AND datetime(updated_at) < datetime('now', '-' || ? || ' days')
@@ -167,18 +167,19 @@ func (s *SQLiteStorage) GetStaleIssues(ctx context.Context, filter types.StaleFi
var compactedAt sql.NullTime var compactedAt sql.NullTime
var compactedAtCommit sql.NullString var compactedAtCommit sql.NullString
var originalSize sql.NullInt64 var originalSize sql.NullInt64
var closeReason sql.NullString
err := rows.Scan( err := rows.Scan(
&issue.ID, &contentHash, &issue.Title, &issue.Description, &issue.Design, &issue.ID, &contentHash, &issue.Title, &issue.Description, &issue.Design,
&issue.AcceptanceCriteria, &issue.Notes, &issue.Status, &issue.AcceptanceCriteria, &issue.Notes, &issue.Status,
&issue.Priority, &issue.IssueType, &assignee, &estimatedMinutes, &issue.Priority, &issue.IssueType, &assignee, &estimatedMinutes,
&issue.CreatedAt, &issue.UpdatedAt, &closedAt, &externalRef, &sourceRepo, &issue.CreatedAt, &issue.UpdatedAt, &closedAt, &externalRef, &sourceRepo,
&compactionLevel, &compactedAt, &compactedAtCommit, &originalSize, &compactionLevel, &compactedAt, &compactedAtCommit, &originalSize, &closeReason,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to scan stale issue: %w", err) return nil, fmt.Errorf("failed to scan stale issue: %w", err)
} }
if contentHash.Valid { if contentHash.Valid {
issue.ContentHash = contentHash.String issue.ContentHash = contentHash.String
} }
@@ -210,10 +211,13 @@ func (s *SQLiteStorage) GetStaleIssues(ctx context.Context, filter types.StaleFi
if originalSize.Valid { if originalSize.Valid {
issue.OriginalSize = int(originalSize.Int64) issue.OriginalSize = int(originalSize.Int64)
} }
if closeReason.Valid {
issue.CloseReason = closeReason.String
}
issues = append(issues, &issue) issues = append(issues, &issue)
} }
return issues, rows.Err() return issues, rows.Err()
} }

View File

@@ -253,7 +253,7 @@ func (t *sqliteTxStorage) GetIssue(ctx context.Context, id string) (*types.Issue
SELECT id, content_hash, title, description, design, acceptance_criteria, notes, SELECT id, content_hash, title, description, design, acceptance_criteria, notes,
status, priority, issue_type, assignee, estimated_minutes, status, priority, issue_type, assignee, estimated_minutes,
created_at, updated_at, closed_at, external_ref, created_at, updated_at, closed_at, external_ref,
compaction_level, compacted_at, compacted_at_commit, original_size, source_repo compaction_level, compacted_at, compacted_at_commit, original_size, source_repo, close_reason
FROM issues FROM issues
WHERE id = ? WHERE id = ?
`, id) `, id)
@@ -1026,7 +1026,7 @@ func (t *sqliteTxStorage) SearchIssues(ctx context.Context, query string, filter
SELECT id, content_hash, title, description, design, acceptance_criteria, notes, SELECT id, content_hash, title, description, design, acceptance_criteria, notes,
status, priority, issue_type, assignee, estimated_minutes, status, priority, issue_type, assignee, estimated_minutes,
created_at, updated_at, closed_at, external_ref, created_at, updated_at, closed_at, external_ref,
compaction_level, compacted_at, compacted_at_commit, original_size, source_repo compaction_level, compacted_at, compacted_at_commit, original_size, source_repo, close_reason
FROM issues FROM issues
%s %s
ORDER BY priority ASC, created_at DESC ORDER BY priority ASC, created_at DESC
@@ -1061,13 +1061,14 @@ func scanIssueRow(row scanner) (*types.Issue, error) {
var originalSize sql.NullInt64 var originalSize sql.NullInt64
var sourceRepo sql.NullString var sourceRepo sql.NullString
var compactedAtCommit sql.NullString var compactedAtCommit sql.NullString
var closeReason sql.NullString
err := row.Scan( err := row.Scan(
&issue.ID, &contentHash, &issue.Title, &issue.Description, &issue.Design, &issue.ID, &contentHash, &issue.Title, &issue.Description, &issue.Design,
&issue.AcceptanceCriteria, &issue.Notes, &issue.Status, &issue.AcceptanceCriteria, &issue.Notes, &issue.Status,
&issue.Priority, &issue.IssueType, &assignee, &estimatedMinutes, &issue.Priority, &issue.IssueType, &assignee, &estimatedMinutes,
&issue.CreatedAt, &issue.UpdatedAt, &closedAt, &externalRef, &issue.CreatedAt, &issue.UpdatedAt, &closedAt, &externalRef,
&issue.CompactionLevel, &compactedAt, &compactedAtCommit, &originalSize, &sourceRepo, &issue.CompactionLevel, &compactedAt, &compactedAtCommit, &originalSize, &sourceRepo, &closeReason,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to scan issue: %w", err) return nil, fmt.Errorf("failed to scan issue: %w", err)
@@ -1101,6 +1102,9 @@ func scanIssueRow(row scanner) (*types.Issue, error) {
if sourceRepo.Valid { if sourceRepo.Valid {
issue.SourceRepo = sourceRepo.String issue.SourceRepo = sourceRepo.String
} }
if closeReason.Valid {
issue.CloseReason = closeReason.String
}
return &issue, nil return &issue, nil
} }