Skip to main content

/moai clean

UPDATED 2026-08-20 8 min read EDIT ON GITHUB ↗

The dead-code identification and safe-removal command. Through static analysis and usage-graph analysis, it finds unused code and removes it safely.

Info
One-line summary: /moai clean is a “code diet tool.” It automatically finds and safely deletes unused functions, variables, imports, and files.
Info
Slash command: In Claude Code, type /moai:clean to run this command directly. Typing just /moai shows the list of all available subcommands.

Overview

As a project grows, code that is no longer used piles up. Unused imports, functions that are never called, and unreferenced types make the codebase complicated. /moai clean finds this dead code with static analysis and removes it safely, verified by tests.

From the harness-engineering perspective, this command plays the role of garbage collection. Dead code is a burden not only for humans but also for agents. Every line of code an agent reads is context (tokens), so removing dead code is code hygiene and, at the same time, a way to trim context and save cost.

Usage

bash
# Basic usage
> /moai clean

# Preview (check only, no modifications)
> /moai clean --dry

# Remove safe items only
> /moai clean --safe-only

# Analyze specific files/directories only
> /moai clean --file src/auth/

# Analyze specific code types only
> /moai clean --type functions

Supported Flags

FlagDescriptionExample
--dry (or --dry-run)Show analysis results only, no removal/moai clean --dry
--safe-onlyRemove only certain dead code (skip uncertain items)/moai clean --safe-only
--file PATHAnalyze only a specific file or directory/moai clean --file src/utils/
--type TYPEAnalyze only a specific code type/moai clean --type imports
--aggressiveInclude low-usage code (where the single caller is itself dead code)/moai clean --aggressive

–type Flag Options

TypeDescription
functionsFunctions/methods that are never called
importsImport statements that are never referenced
typesUnused type definitions
variablesVariables declared but never used
filesFiles not imported anywhere

The –dry Flag

Previews which items are classified as dead code without modifying any actual code:

bash
> /moai clean --dry

This option is useful when you want to review the analysis results before removal.

Execution Flow

/moai clean runs in 7 steps.

flowchart TD
    Start["/moai clean run"] --> Phase1["Step 1: static analysis scan"]

    Phase1 --> Phase2["Step 2: usage-graph analysis and classification"]
    Phase2 --> Classify{"Classification result"}
    Classify --> Dead["Certain dead code"]
    Classify --> TestOnly["Test-only"]
    Classify --> Likely["Likely dead code"]
    Classify --> False["False positive (actually in use)"]

    Dead --> Phase3{"Step 3: removal-plan approval
(AskUserQuestion / --dry?)"} Phase3 -->|--dry or reject| Report["Show analysis results and exit"] Phase3 -->|Approve| Phase4["Step 4: safe removal"] Phase4 --> Phase5["Step 5: test verification"] Phase5 --> Pass{"Tests pass?"} Pass -->|No| Rollback["Roll back and retry"] Pass -->|Yes| Phase6["Step 6: MX tag cleanup"] Rollback --> Phase6 Phase6 --> Phase7["Step 7: report"]

Step 3 removal-plan approval is a human gate where the orchestrator presents the removal-target list to the user via AskUserQuestion and obtains approval. Step 6 MX tag cleanup also cleans up the @MX comments attached to the removed code, so no dangling comments remain.

Step 1: Static Analysis Scan

Auto-detects the project language by project marker and detects candidates with each language’s standard dead-code analysis tools. It treats the 16 supported languages equally (go, python, typescript, javascript, rust, java, kotlin, csharp, ruby, php, elixir, cpp, scala, r, flutter, swift), gracefully skips tools that are not installed, and silently passes projects with no recognized language marker. The following are representative examples only and do not favor any particular language:

Language (example)Analysis tools (example)Checks
Gogo vet, staticcheck, deadcodeUnused variables, functions, types
Pythonvulture, autoflakeDead code, unused imports
TypeScript/JavaScriptts-prune, ESLint no-unused-varsUnused exports, variables
Rustcargo clippy, cargo udepsDead-code warnings, unused dependencies

The remaining 12 languages (java, kotlin, csharp, ruby, php, elixir, cpp, scala, r, flutter, swift, etc.) are scanned equally with their own standard toolchains.

Scan categories:

  • Unused imports: import statements with no references
  • Unused variables: variables declared but never read
  • Unused functions: functions defined but never called
  • Unused types: type definitions with no usage sites
  • Unused files: files not imported anywhere
  • Dead dependencies: packages installed but never imported

Step 2: Usage-Graph Analysis

Builds a usage graph to verify the static analysis results:

  • Searches the entire codebase for references to each candidate
  • Checks indirect usage (interfaces, reflection, dynamic dispatch)
  • Checks test-only usage (used only in tests, unused in production code)
  • Checks conditional compilation (build tags, environment-based imports)

Step 3: Classification

ClassificationDescriptionRemoval safety
Certain dead codeNo references anywhere in the codebaseSafe
Test-onlyUsed only in test filesMostly safe
Likely dead codeLow confidence (possible dynamic usage)Caution needed
False positiveActually in use (reflection, plugins, etc.)Cannot remove

Step 4: Safe Removal

Removes in reverse dependency-graph order (leaf nodes first):

  • Removes related code as a group (function + private helpers)
  • Updates affected imports
  • Cleans up files left empty after all exports are removed
  • Code with an @MX:ANCHOR tag is never removed without explicit approval

Step 5: Test Verification

After removal, the full test suite runs to verify against regressions. If tests fail, the removal is rolled back and classified as a “false positive.” Safety is judged by the evidence of passing tests — not by “I deleted it and it seems fine.”

Step 6: Report

text
Dead-code removal report

Removed: 15 items (287 lines)
  - src/utils/helper.go: UnusedFunction (15 lines)
  - src/models/old.go: entire file deleted (120 lines)

Kept (false positives): 2 items
  - src/api/handler.go: DynamicHandler (uses reflection)

Test result: PASS (all tests pass)

Codebase reduction:
  - Files removed: 3
  - Lines removed: 287
  - Dependencies removed: 1

Agent Delegation Chain

/moai clean runs by spawning an Agent(general-purpose) refactoring specialist twice (not a dedicated named agent — a general-purpose agent that receives the refactoring whitelist and ANALYZE-PRESERVE-IMPROVE instructions injected at spawn time). Steps 1–2 are one combined spawn, steps 4–5 another, and step 6 the orchestrator handles directly with no spawn.

flowchart TD
    User["User request"] --> MoAI["MoAI orchestrator"]
    MoAI --> Refactor1["Agent(general-purpose) refactoring specialist
static analysis + usage graph (combined spawn 1)"] Refactor1 --> MoAI2["MoAI orchestrator
user approval"] MoAI2 --> Refactor2["Agent(general-purpose) refactoring specialist
safe removal + test verification (combined spawn 2)"] Refactor2 --> MoAI3["MoAI orchestrator
@MX tag cleanup (direct)"] MoAI3 --> Complete["Done"]
AgentRoleMain work
Agent(general-purpose) refactoring specialist (spawn 1)AnalysisStatic analysis + usage graph (steps 1–2 combined)
Agent(general-purpose) refactoring specialist (spawn 2)Removal and verificationSafe removal + running the test suite and checking regressions (steps 4–5 combined)
MoAI orchestratorCoordinationUser approval, @MX tag cleanup (step 6, direct)

Frequently Asked Questions

Q: What if dead code is removed by mistake?

You can revert with Git. MoAI removes in reverse dependency order and runs the tests, so if anything goes wrong it rolls back automatically.

Q: When should I use --aggressive?

Use it when you want to include the case where a function has exactly 1 caller and that caller is itself dead code. Useful for cleanup after large refactorings.

Q: Is code used via reflection removed too?

In --safe-only mode, only “certain dead code” is removed. Code used via reflection or dynamic dispatch is classified as a “false positive” and preserved.

A different surface — moai clean --home (home-directory cleanup)

Info
The terminal CLI moai clean --home shares the name but not the target — unlike /moai clean above (project dead code), this one tidies the ~/.moai home directory. It is not a slash command and does no dead-code analysis.

Session state, caches, logs, and stale profiles pile up in ~/.moai. moai clean --home tidies only the cleanup-target directories listed in its allowlist — anything not on the list stays, untouched and unasked. ~/.claude is never touched.

bash
# Cleanup conversation — dry-run by default (report only, no deletion)
$ moai clean --home

# Real deletion — guarded force
$ moai clean --home --force
  • Dry-run is the default. Deletion requires an explicit --force, and even that operates only inside the allowlist.
  • Before deleting, the Home Disk Usage check in moai doctor reports how full things are — an advisory check whose thresholds follow the compiled defaults.
  • To relocate ~/.moai itself, point MOAI_HOME at a new home root (only a non-empty absolute path is honored; an empty value equals unset and relative paths are disregarded). Note that only the Go binary reads this variable — shell hooks do not honor it.

The four allowlisted categories

CategoryTargetCondition
debugentries under claude-profiles/<profile>/debug/past the retention window
releasesrelease binaries in releases/ (plus their paired .sha256)everything except the current version and the 3 newest of the rest. version.json and LATEST are never candidates
logsfiles in the root logs/past the retention window
backupsbackups/removed-* directoriespast the retention window

Anything not on the list is invisible to the scanner. And the carve-outs (config/, state/, projects/, worktrees/, mcp/, bin/, search/, studio/, plugins/, plus launch.yaml, preferences.yaml, and every file whose name starts with credentials) win inside the allowlist too: if an aged backups/removed-* holds even one such file, the whole directory is skipped. ~/.claude is not read even under --force.

state.home_retention_days

The retention window is read only from the HOME tier file ~/.moai/config/sections/state.yaml. It is a different key on a different tier from a project’s state.retention_days — there is one home but many projects, and separating the read site stops two projects from cleaning the same home with two different windows.

ValueBehavior
Key absent / file absentThe default of 30 days
Positive integerOnly entries older than that many days become candidates
0Cleaning disabled — no candidate is produced

The full story: Home Directory Hygiene.