Skip to content

feat: clean stale and orphaned indexes - #180

Merged
aeneasr merged 2 commits into
mainfrom
aeneasr/purge-stale-indices
Aug 6, 2026
Merged

feat: clean stale and orphaned indexes#180
aeneasr merged 2 commits into
mainfrom
aeneasr/purge-stale-indices

Conversation

@aeneasr

@aeneasr aeneasr commented Jul 31, 2026

Copy link
Copy Markdown
Member

Replaces lumen purge with lumen clean, which removes orphaned indexes and indexes unused beyond a configurable cutoff while preserving indexes that are actively being written. Records last-access timestamps when stores open, with read-only metadata scans and a last-indexed fallback for legacy indexes. Updates command documentation and reindex guidance, with expanded cleanup and store coverage. Tested with make test and make lint.

Summary by CodeRabbit

  • New Features

    • Added lumen clean to remove stale or orphaned indexes.
    • Supports age-based cleanup, including --days 0 for all unlocked indexes.
    • Preserves indexes currently in use and reports skipped or failed removals.
    • Index access times are now tracked to improve cleanup accuracy.
  • Documentation

    • Updated cleanup and reindexing guidance to use lumen clean and lumen index --force.
    • Removed references to the previous purge command.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@aeneasr, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 58 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 88b7652f-49be-4bdd-b9fc-9184a93abc2f

📥 Commits

Reviewing files that changed from the base of the PR and between b104b46 and 38f048d.

📒 Files selected for processing (1)
  • .github/workflows/ci.yml
📝 Walkthrough

Walkthrough

The PR replaces the purge workflow with lumen clean, adds stale-index detection and removal, tracks store access timestamps, updates metadata queries, and revises cleanup and reindex documentation.

Changes

Index cleanup

Layer / File(s) Summary
Store access metadata
internal/store/store.go, internal/store/store_test.go
The store records last_accessed_at on open. Metadata reads now support multiple keys. Tests cover timestamps, busy databases, and read-only access.
Clean command and validation
cmd/clean.go, cmd/clean_test.go
lumen clean removes stale or orphaned indexes, preserves locked indexes, supports --days 0, and validates command input. Tests cover cleanup decisions and edge cases.
Command documentation updates
CLAUDE.md, README.md, skills/reindex/SKILL.md
Documentation replaces lumen purge with lumen clean and uses lumen index --force . for forced rebuilds.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CleanCommand
  participant LumenDataDirectory
  participant SQLiteIndexMetadata
  User->>CleanCommand: Run lumen clean
  CleanCommand->>LumenDataDirectory: Scan index directories
  CleanCommand->>SQLiteIndexMetadata: Read access and index timestamps
  CleanCommand->>LumenDataDirectory: Remove stale unlocked indexes
  CleanCommand-->>User: Report removals and skips
Loading

Possibly related PRs

  • ory/lumen#135: Directly replaces the earlier purge command and related path-based purge functionality.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding cleanup for stale and orphaned indexes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch aeneasr/purge-stale-indices

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@aeneasr
aeneasr marked this pull request as ready for review August 5, 2026 13:04

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
cmd/clean.go (1)

88-131: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Route interactive status through tui.Progress.

The coding guidelines require interactive commands including clean to send progress and status to tui.Progress on stderr. This sweep writes status lines directly with fmt.Fprintf on the stderr writer. The summary on stdout at Line 130 already matches the guideline.

The injected io.Writer pair keeps cleanIndexes testable, so keep the writers and emit through tui.Progress in runClean, or confirm that tui.Progress is intended only for long-running progress reporting.

As per coding guidelines: "Interactive commands such as index, clean, and search must send progress and status to tui.Progress on stderr, completion summaries to stdout with fmt.Printf, and errors to stderr with fmt.Fprintf(os.Stderr, ...)."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/clean.go` around lines 88 - 131, Route the interactive status messages in
cleanIndexes, including lock skips, removal failures, and successful removals,
through tui.Progress on stderr instead of direct fmt.Fprintf calls. Preserve the
injected stdout and stderr writers for testability, keep the completion summary
on stdout, and leave error reporting semantics unchanged.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/clean.go`:
- Line 94: Bound the positive days value in runClean before calculating cutoff,
using a maximum that keeps time.Duration(days) * 24 * time.Hour from
overflowing; preserve the existing negative-value validation and ensure larger
retention windows are rejected or safely clamped rather than allowing cutoff to
wrap and mark every index stale.
- Around line 107-125: Update the cleanup flow around isIndexStale and
os.RemoveAll to call indexlock.TryAcquire for the database lock instead of
checking indexlock.IsHeld. Retain each successfully acquired lock through the
stale check and hashDir deletion, release it on every path, and treat
acquisition failure as the existing held-lock skip case.

In `@README.md`:
- Line 401: Update the descriptions of `lumen clean --days 0` in README.md lines
401-401 and skills/reindex/SKILL.md lines 22-23 to state that it removes all
eligible cached indexes while preserving indexes held by active indexers through
active locks.

---

Nitpick comments:
In `@cmd/clean.go`:
- Around line 88-131: Route the interactive status messages in cleanIndexes,
including lock skips, removal failures, and successful removals, through
tui.Progress on stderr instead of direct fmt.Fprintf calls. Preserve the
injected stdout and stderr writers for testability, keep the completion summary
on stdout, and leave error reporting semantics unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 98c2f1df-b5c6-40c4-aca5-364ae8891ff9

📥 Commits

Reviewing files that changed from the base of the PR and between d0dee0e and b104b46.

📒 Files selected for processing (9)
  • CLAUDE.md
  • README.md
  • cmd/clean.go
  • cmd/clean_test.go
  • cmd/purge.go
  • cmd/purge_test.go
  • internal/store/store.go
  • internal/store/store_test.go
  • skills/reindex/SKILL.md
💤 Files with no reviewable changes (2)
  • cmd/purge.go
  • cmd/purge_test.go

Comment thread cmd/clean.go
return fmt.Errorf("read data dir: %w", err)
}

cutoff := now.Add(-time.Duration(days) * 24 * time.Hour)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Bound --days to prevent a time.Duration overflow that deletes every index.

time.Duration holds nanoseconds in an int64, so it saturates at about 106751 days. time.Duration(days) * 24 * time.Hour overflows for a larger value. The cutoff then wraps to a time far in the future, isIndexStale marks every index stale, and the sweep deletes all of them.

A larger retention window must never delete more. runClean already rejects negative values at Line 72. Add an upper bound there or clamp the cutoff here.

🐛 Proposed fix in runClean
 	if days < 0 {
 		return fmt.Errorf("--days must not be negative, got %d", days)
 	}
+	const maxCleanDays = 100000 // keeps days*24h inside time.Duration's range
+	if days > maxCleanDays {
+		return fmt.Errorf("--days must not exceed %d, got %d", maxCleanDays, days)
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/clean.go` at line 94, Bound the positive days value in runClean before
calculating cutoff, using a maximum that keeps time.Duration(days) * 24 *
time.Hour from overflowing; preserve the existing negative-value validation and
ensure larger retention windows are rejected or safely clamped rather than
allowing cutoff to wrap and mark every index stale.

Comment thread cmd/clean.go
Comment on lines +107 to +125
if indexlock.IsHeld(indexlock.LockPathForDB(dbPath)) {
_, _ = fmt.Fprintf(stderr, "Keeping %s: an indexer is currently running.\n", entry.Name())
skipped++
continue
}

stale, reason := isIndexStale(dbPath, days, cutoff)
if !stale {
skipped++
continue
}
if err := os.RemoveAll(hashDir); err != nil {
_, _ = fmt.Fprintf(stderr, "Failed to remove %s: %v\n", hashDir, err)
if firstErr == nil {
firstErr = fmt.Errorf("remove %s: %w", hashDir, err)
}
skipped++
continue
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the indexlock API: TryAcquire, IsHeld, Release, and where LockPathForDB places the lock file.
rg -nP --type=go -B 2 -A 25 'func (TryAcquire|IsHeld|LockPathForDB)\(|func \(.*Lock\) Release\(' internal/indexlock

Repository: ory/lumen

Length of output: 3971


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== cleanIndexes implementation =="
sed -n '80,135p' cmd/clean.go

echo
echo "== relevant indexlock tests =="
rg -n --type=go -B 4 -A 20 'TryAcquire|IsHeld|lock file|lock path|LockPathForDB|flock' cmd/clean_test.go internal/indexlock

echo
echo "== lock implementations =="
sed -n '1,90p' internal/indexlock/lock.go

Repository: ory/lumen

Length of output: 20577


Hold the index lock while cleaning the index directory.

indexlock.IsHeld checks the lock before os.RemoveAll deletes hashDir. An indexer can acquire the lock in that window, so clean can remove the index files it is currently writing to.

Use indexlock.TryAcquire(indexlock.LockPathForDB(dbPath)) instead and keep any acquired lock for the whole stale/delete decision. cmd/clean_test.go already exercises the held-lock case with TryAcquire.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/clean.go` around lines 107 - 125, Update the cleanup flow around
isIndexStale and os.RemoveAll to call indexlock.TryAcquire for the database lock
instead of checking indexlock.IsHeld. Retain each successfully acquired lock
through the stale check and hashDir deletion, release it on every path, and
treat acquisition failure as the existing held-lock skip case.

Comment thread README.md
```bash
lumen clean # remove indexes unused for 30 days or whose project is gone
lumen clean --days 7 # tighten the cutoff to a week
lumen clean --days 0 # remove every cached index on this host

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

lumen clean --days 0 is not an unconditional full wipe.

cmd/clean.go:84-133 preserves indexes with active locks. Update both descriptions to state that the command removes all eligible indexes while keeping indexes used by active indexers.

  • README.md#L401-L401: replace “remove every cached index” with the eligible-index behavior.
  • skills/reindex/SKILL.md#L22-L23: replace “deletes every cached index” with the same active-lock exception.
📍 Affects 2 files
  • README.md#L401-L401 (this comment)
  • skills/reindex/SKILL.md#L22-L23
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 401, Update the descriptions of `lumen clean --days 0` in
README.md lines 401-401 and skills/reindex/SKILL.md lines 22-23 to state that it
removes all eligible cached indexes while preserving indexes held by active
indexers through active locks.

@aeneasr
aeneasr enabled auto-merge (squash) August 6, 2026 12:12
@aeneasr
aeneasr merged commit f62bf57 into main Aug 6, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant