Fix blocked cache invalidation in transaction operations (bd-1c4h)

Transaction operations weren't invalidating the blocked_issues_cache,
causing GetReadyWork to return stale results after transactional changes.

Changes:
- Refactor invalidateBlockedCache to accept execer interface (supports
  both *sql.Tx and *sql.Conn)
- Add cache invalidation in transaction.go for:
  - UpdateIssue (when status changes)
  - CloseIssue (always - closed issues don't block
  - AddDependency (for DepBlocks/DepParentChild types)
  - RemoveDependency (queries type before deletion)

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

Co-Authored-By: Claude <noreply@anthropic.com>
EOF
)
This commit is contained in:
Steve Yegge
2025-11-24 14:03:11 -08:00
parent 0acd9d0a5d
commit a8d7d6575c
3 changed files with 48 additions and 8 deletions

View File

@@ -100,11 +100,10 @@ type execer interface {
// rebuildBlockedCache completely rebuilds the blocked_issues_cache table
// This is used during cache invalidation when dependencies change
func (s *SQLiteStorage) rebuildBlockedCache(ctx context.Context, tx *sql.Tx) error {
// Use the transaction if provided, otherwise use direct db connection
var exec execer = s.db
if tx != nil {
exec = tx
func (s *SQLiteStorage) rebuildBlockedCache(ctx context.Context, exec execer) error {
// Use direct db connection if no execer provided
if exec == nil {
exec = s.db
}
// Clear the cache
@@ -152,6 +151,6 @@ func (s *SQLiteStorage) rebuildBlockedCache(ctx context.Context, tx *sql.Tx) err
// invalidateBlockedCache rebuilds the blocked issues cache
// Called when dependencies change or issue status changes
func (s *SQLiteStorage) invalidateBlockedCache(ctx context.Context, tx *sql.Tx) error {
return s.rebuildBlockedCache(ctx, tx)
func (s *SQLiteStorage) invalidateBlockedCache(ctx context.Context, exec execer) error {
return s.rebuildBlockedCache(ctx, exec)
}

View File

@@ -414,6 +414,14 @@ func (t *sqliteTxStorage) UpdateIssue(ctx context.Context, id string, updates ma
return fmt.Errorf("failed to mark issue dirty: %w", err)
}
// Invalidate blocked issues cache if status changed (bd-1c4h)
// Status changes affect which issues are blocked (blockers must be open/in_progress/blocked)
if _, statusChanged := updates["status"]; statusChanged {
if err := t.parent.invalidateBlockedCache(ctx, t.conn); err != nil {
return fmt.Errorf("failed to invalidate blocked cache: %w", err)
}
}
return nil
}
@@ -499,6 +507,12 @@ func (t *sqliteTxStorage) CloseIssue(ctx context.Context, id string, reason stri
return fmt.Errorf("failed to mark issue dirty: %w", err)
}
// Invalidate blocked issues cache since status changed to closed (bd-1c4h)
// Closed issues don't block others, so this affects blocking calculations
if err := t.parent.invalidateBlockedCache(ctx, t.conn); err != nil {
return fmt.Errorf("failed to invalidate blocked cache: %w", err)
}
return nil
}
@@ -646,11 +660,30 @@ func (t *sqliteTxStorage) AddDependency(ctx context.Context, dep *types.Dependen
return fmt.Errorf("failed to mark depends-on issue dirty: %w", err)
}
// Invalidate blocked cache for blocking dependencies (bd-1c4h)
if dep.Type == types.DepBlocks || dep.Type == types.DepParentChild {
if err := t.parent.invalidateBlockedCache(ctx, t.conn); err != nil {
return fmt.Errorf("failed to invalidate blocked cache: %w", err)
}
}
return nil
}
// RemoveDependency removes a dependency within the transaction.
func (t *sqliteTxStorage) RemoveDependency(ctx context.Context, issueID, dependsOnID string, actor string) error {
// First, check what type of dependency is being removed (bd-1c4h)
var depType types.DependencyType
err := t.conn.QueryRowContext(ctx, `
SELECT type FROM dependencies WHERE issue_id = ? AND depends_on_id = ?
`, issueID, dependsOnID).Scan(&depType)
// Store whether cache needs invalidation before deletion
needsCacheInvalidation := false
if err == nil {
needsCacheInvalidation = (depType == types.DepBlocks || depType == types.DepParentChild)
}
result, err := t.conn.ExecContext(ctx, `
DELETE FROM dependencies WHERE issue_id = ? AND depends_on_id = ?
`, issueID, dependsOnID)
@@ -684,6 +717,13 @@ func (t *sqliteTxStorage) RemoveDependency(ctx context.Context, issueID, depends
return fmt.Errorf("failed to mark depends-on issue dirty: %w", err)
}
// Invalidate blocked cache if this was a blocking dependency (bd-1c4h)
if needsCacheInvalidation {
if err := t.parent.invalidateBlockedCache(ctx, t.conn); err != nil {
return fmt.Errorf("failed to invalidate blocked cache: %w", err)
}
}
return nil
}