Skip to content

feat(mcp-plans): add automatic background cleanup of inactive plans - #656

Merged
dobesv merged 3 commits into
mainfrom
plan-cleanup
May 25, 2026
Merged

feat(mcp-plans): add automatic background cleanup of inactive plans#656
dobesv merged 3 commits into
mainfrom
plan-cleanup

Conversation

@dobesv

@dobesv dobesv commented May 25, 2026

Copy link
Copy Markdown
Owner

Adds automatic background cleanup of inactive plans to harnx-mcp-plans with a configurable retention period. Cleanup is performed daily in the background.

Key changes:

  • Add --retention-days CLI flag and AGENT_PLANS_RETENTION_DAYS env var (default 14 days)
  • Implement supervised background task that restarts on panic
  • Add unit tests for deletion logic

#652

Plan: harnx-mcp-plans-auto-cleanup

Summary by CodeRabbit

  • New Features

    • Automatic cleanup of inactive plans with configurable retention period
    • Added --retention-days command-line option and environment variable support for retention configuration
    • Background cleanup task supervision with automatic restart capability
  • Documentation

    • Added README documenting the plans management server and available tools
    • Added solution guide for async task supervision patterns

Review Change Stack

Adds automatic background cleanup of inactive plans to harnx-mcp-plans
with a configurable retention period. Cleanup is performed daily in the
background.

Key changes:
- Add --retention-days CLI flag and AGENT_PLANS_RETENTION_DAYS env var
  (default 14 days)
- Implement supervised background task that restarts on panic
- Add unit tests for deletion logic

#652

Plan: harnx-mcp-plans-auto-cleanup
@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@dobesv, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 34 minutes and 38 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

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.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fbf86907-419b-4252-86d9-2b10998fc9a4

📥 Commits

Reviewing files that changed from the base of the PR and between d3838cc and 8698758.

📒 Files selected for processing (2)
  • crates/harnx-mcp-plans/src/main.rs
  • crates/harnx-mcp-plans/src/server.rs
📝 Walkthrough

Walkthrough

This PR adds inactivity-based cleanup to the harnx-mcp-plans MCP server. It enables the tokio time feature, extends CLI argument parsing to support --retention-days configuration with environment variable override, implements a supervised background cleanup task that removes plan directories exceeding the retention age, and updates MCP tool metadata templates.

Changes

Plan Cleanup and Task Supervision

Layer / File(s) Summary
Dependencies and documentation setup
crates/harnx-mcp-plans/Cargo.toml, crates/harnx-mcp-plans/README.md, docs/solutions/async-patterns/mcp-server-background-task-supervision-2026-05-25.md
Enable tokio time feature for interval scheduling; document server retention/cleanup behavior and the async task supervision pattern using tokio::select! for concurrent service and cleanup task management.
Retention configuration via CLI and environment
crates/harnx-mcp-plans/src/main.rs
Add --retention-days/-r CLI flag and AGENT_PLANS_RETENTION_DAYS environment variable support with validation; resolve retention using precedence CLI > env > default (14 days), returning (plans_dir, retention_days) tuple.
Main entry point with cleanup task supervision
crates/harnx-mcp-plans/src/main.rs
Update main to spawn cleanup loop alongside MCP service using supervised restart pattern: cleanup task is restarted if it exits while service remains active; cleanup is disabled when retention is zero.
Cleanup implementation with last-activity detection and tests
crates/harnx-mcp-plans/src/server.rs
Implement plan_last_activity to detect the most recent file modification across plan.md, tasks/*.md, and notes/*.md with fallback to directory mtime. Implement cleanup_loop that runs initial cleanup and repeats daily via tokio::task::spawn_blocking for filesystem operations. Add unit tests for last-activity computation with multiple file types and fallback, and async integration test verifying stale plans are deleted while recent plans are retained.
MCP tool call_template metadata updates
crates/harnx-mcp-plans/src/server.rs
Adjust call_template formatting and body truncation for update_plan, add_task, update_task, and add_note tools in MCP tool metadata.

Sequence Diagram

sequenceDiagram
  participant Main
  participant CleanupTask as cleanup_loop<br/>(task)
  participant Interval
  participant BlockingFS as spawn_blocking<br/>(fs)
  participant PlansDir as Plans Directory
  
  Main->>CleanupTask: spawn cleanup_loop(dir, retention_days)
  CleanupTask->>Interval: create daily interval
  Interval-->>CleanupTask: first tick (immediate)
  CleanupTask->>BlockingFS: run_cleanup_pass(dir, retention_days)
  BlockingFS->>PlansDir: compute last_activity for each plan
  BlockingFS->>PlansDir: delete plans exceeding retention
  BlockingFS-->>CleanupTask: cleanup complete
  Interval-->>CleanupTask: next tick (24h later)
  CleanupTask->>BlockingFS: run_cleanup_pass again
  Note over Main,CleanupTask: If cleanup_loop exits, Main respawns it<br/>If MCP service exits, shutdown proceeds
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • dobesv/harnx#516: Main PR's call_template metadata updates to update_plan/add_task/update_task/add_note tools are coupled to server-side tool behavior changes in the retrieved PR.

Suggested reviewers

  • codescene-delta-analysis

Poem

🐇 A cleanup hare hops through the plans directory each dawn,
Removing forgotten tasks from days long gone,
While the supervisor keeps both service and sweeper alive,
With select! and supervised restarts, together they thrive!
No stale plans shall linger—the retention window has spoken. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.75% 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 'feat(mcp-plans): add automatic background cleanup of inactive plans' directly and clearly summarizes the main change: adding automatic cleanup functionality for inactive plans in the mcp-plans service.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch plan-cleanup

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 and usage tips.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/harnx-mcp-plans/src/main.rs (1)

63-159: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return parse errors via anyhow::Result instead of calling std::process::exit.

parse_args currently hard-exits in multiple branches, which bypasses main’s anyhow::Result<()> flow and violates the repo error-handling rule. This also contributes to the current complexity gate failure on this method.

Proposed direction
- fn parse_args() -> (PathBuf, u64) {
+ fn parse_args() -> anyhow::Result<(PathBuf, u64)> {
...
-                    eprintln!("harnx-mcp-plans: --dir requires a path argument");
-                    std::process::exit(1);
+                    anyhow::bail!("harnx-mcp-plans: --dir requires a path argument");
...
-                eprintln!("harnx-mcp-plans: unknown argument: {}", other);
-                eprintln!("Try: harnx-mcp-plans --help");
-                std::process::exit(1);
+                anyhow::bail!("harnx-mcp-plans: unknown argument: {other}. Try: harnx-mcp-plans --help");
...
-    (plans_dir, retention_days)
+    Ok((plans_dir, retention_days))
}
- let (plans_dir, retention_days) = parse_args();
+ let (plans_dir, retention_days) = parse_args()?;

As per coding guidelines: "**/*.rs: Use anyhow::Result and anyhow::bail! for error handling throughout the codebase".

🤖 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 `@crates/harnx-mcp-plans/src/main.rs` around lines 63 - 159, parse_args
currently calls std::process::exit in many branches; change it to return
anyhow::Result<(PathBuf,u64)> and replace all hard exits with anyhow::bail! (or
Err(anyhow!())) carrying the same human-readable messages (e.g., when missing
flag args, invalid --retention-days, unknown argument, invalid
AGENT_PLANS_RETENTION_DAYS value, and for --help/ -h show usage via Err or a
distinct Help variant if desired), keep the same validation logic and messages,
and ensure callers (main) propagate/handle the Result so process termination
happens at a single top-level spot; update the function signature and all return
points to use ?/bail! instead of std::process::exit.
🤖 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 `@crates/harnx-mcp-plans/src/main.rs`:
- Around line 48-55: The cleanup supervision currently respawns
server::cleanup_loop immediately inside the result = &mut cleanup_handle match
arm, which can hot-loop on deterministic failures; add a restart backoff (e.g.,
exponential with cap and small jitter) before calling tokio::spawn again.
Implement a mutable backoff Duration variable near cleanup_handle (reset to base
on successful run and double on each failure up to a max), await
tokio::time::sleep(backoff).await in the Err(e) branch (referencing
cleanup_handle, cleanup_dir.clone(), retention_days) and reset the backoff after
a successful spawn or clear on normal exit.

In `@crates/harnx-mcp-plans/src/server.rs`:
- Around line 1986-2030: The loop in async fn run_cleanup_pass uses
plan_dirs(dir) synchronously which can block the runtime; wrap the directory
listing in a blocking task instead: call tokio::task::spawn_blocking(move ||
plan_dirs(dir).collect::<Vec<_>>()) and await it to obtain a Vec<PathBuf>, then
iterate that Vec (instead of calling plan_dirs directly) so only non-blocking
tokio-await operations remain in run_cleanup_pass; keep the existing
spawn_blocking usage for plan_last_activity and remove any direct synchronous
read_dir calls from the async context (refer to run_cleanup_pass and
plan_dirs/plan_last_activity to locate the changes).

---

Outside diff comments:
In `@crates/harnx-mcp-plans/src/main.rs`:
- Around line 63-159: parse_args currently calls std::process::exit in many
branches; change it to return anyhow::Result<(PathBuf,u64)> and replace all hard
exits with anyhow::bail! (or Err(anyhow!())) carrying the same human-readable
messages (e.g., when missing flag args, invalid --retention-days, unknown
argument, invalid AGENT_PLANS_RETENTION_DAYS value, and for --help/ -h show
usage via Err or a distinct Help variant if desired), keep the same validation
logic and messages, and ensure callers (main) propagate/handle the Result so
process termination happens at a single top-level spot; update the function
signature and all return points to use ?/bail! instead of std::process::exit.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 90d037ab-8785-4f93-ade8-0ff7bf0653cb

📥 Commits

Reviewing files that changed from the base of the PR and between 99d6a7d and d3838cc.

📒 Files selected for processing (5)
  • crates/harnx-mcp-plans/Cargo.toml
  • crates/harnx-mcp-plans/README.md
  • crates/harnx-mcp-plans/src/main.rs
  • crates/harnx-mcp-plans/src/server.rs
  • docs/solutions/async-patterns/mcp-server-background-task-supervision-2026-05-25.md

Comment thread crates/harnx-mcp-plans/src/main.rs
Comment thread crates/harnx-mcp-plans/src/server.rs
codescene-delta-analysis[bot]

This comment was marked as outdated.

@codescene-delta-analysis codescene-delta-analysis 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.

Gates Failed
Enforce advisory code health rules (2 files with Complex Method)

Gates Passed
5 Quality Gates Passed

See analysis details in CodeScene

Reason for failure
Enforce advisory code health rules Violations Code Health Impact
main.rs 1 advisory rule 9.84 → 9.37 Suppress
server.rs 1 advisory rule 4.93 → 4.85 Suppress

Quality Gate Profile: Pay Down Tech Debt
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

}

fn parse_args() -> PathBuf {
fn parse_args() -> anyhow::Result<(PathBuf, u64)> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

❌ New issue: Complex Method
parse_args has a cyclomatic complexity of 12, threshold = 9

Suppress

Comment on lines +1986 to +2039
async fn run_cleanup_pass(dir: &Path, retention: Duration) {
let dir_owned = dir.to_owned();
let dirs = match tokio::task::spawn_blocking(move || plan_dirs(&dir_owned)).await {
Ok(dirs) => dirs,
Err(e) => {
eprintln!("[cleanup] error listing plans: {e}");
return;
}
};

for plan_dir in dirs {
let name = plan_dir
.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned();
let plan_dir_for_activity = plan_dir.clone();
let last_activity =
match tokio::task::spawn_blocking(move || plan_last_activity(&plan_dir_for_activity))
.await
{
Ok(Ok(last_activity)) => last_activity,
Ok(Err(e)) => {
eprintln!("[cleanup] error checking plan {name}: {e}");
continue;
}
Err(e) => {
eprintln!("[cleanup] error checking plan {name}: {e}");
continue;
}
};

let age = std::time::SystemTime::now()
.duration_since(last_activity)
.unwrap_or_default();
if age <= retention {
continue;
}

let plan_dir_for_delete = plan_dir.clone();
match tokio::task::spawn_blocking(move || std::fs::remove_dir_all(plan_dir_for_delete))
.await
{
Ok(Ok(())) => {
eprintln!(
"[cleanup] deleted inactive plan {name} (inactive for {} days)",
age.as_secs() / 86_400
);
}
Ok(Err(e)) => eprintln!("[cleanup] error deleting plan {name}: {e}"),
Err(e) => eprintln!("[cleanup] error deleting plan {name}: {e}"),
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

❌ New issue: Complex Method
run_cleanup_pass has a cyclomatic complexity of 9, threshold = 9

Suppress

@dobesv
dobesv enabled auto-merge (squash) May 25, 2026 20:41
@dobesv
dobesv merged commit b4d8f3b into main May 25, 2026
5 of 6 checks passed
@dobesv
dobesv deleted the plan-cleanup branch May 25, 2026 20:49
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