Skip to content

feat(protection): prevent Ralph from deleting its own .ralph/ directory#182

Merged
frankbria merged 2 commits intomainfrom
feature/issue-149-file-protection
Feb 17, 2026
Merged

feat(protection): prevent Ralph from deleting its own .ralph/ directory#182
frankbria merged 2 commits intomainfrom
feature/issue-149-file-protection

Conversation

@frankbria
Copy link
Owner

@frankbria frankbria commented Feb 17, 2026

Summary

Implements #149: Prevents Ralph from accidentally deleting its own .ralph/ directory and .ralphrc file during cleanup/refactor sessions.

Multi-layered defense strategy:

  • Layer 1 — ALLOWED_TOOLS restriction: Default Bash(git *) replaced with enumerated safe git subcommands (git add, git commit, git diff, etc.), blocking destructive commands like git clean, git rm, git reset
  • Layer 2 — PROMPT.md warning: New "Protected Files (DO NOT MODIFY)" section in all PROMPT.md templates explicitly instructs Claude to never touch .ralph/ or .ralphrc
  • Layer 3 — Pre-loop integrity check: validate_ralph_integrity() runs at startup and before every loop iteration, halting execution with recovery instructions if critical files are missing

Additional fixes:

  • .ralphrc missing is now a CRITICAL failure in ralph-enable verification (was optional warning)
  • New lib/file_protection.sh module with validate_ralph_integrity() and get_integrity_report()

Acceptance Criteria

  • .ralph/ directory protected from deletion during cleanup sessions
  • .ralphrc always generated and validation enforced
  • Cleanup/refactor sessions cannot break Ralph itself
  • Recovery instructions provided if damage is detected (ralph-enable --force)

Test Plan

  • 548 tests passing (38 new tests, 0 failures)
  • New test_file_protection.bats — 22 tests for validation functions
  • New test_integrity_check.bats — 12 tests for pre-loop integrity check
  • Updated existing tests for ALLOWED_TOOLS changes
  • All existing tests pass with no regressions

Implementation Notes

  • Adapted from traycer plan: dropped .ralphignore (Claude doesn't read it), cleanup keyword detection (always-on warnings are better), and recovery command (follow-up issue)
  • The Write and Edit tools cannot be restricted without breaking Claude's ability to work — the PROMPT.md warning is the defense for that vector
  • Users can override to Bash(git *) in .ralphrc if they need the broad permission

Closes #149

Summary by CodeRabbit

Release Notes

  • New Features

    • File integrity validation prevents accidental deletion of critical Ralph configuration files; suggests recovery steps if issues occur
  • Documentation

    • Added guidelines detailing protected configuration files that should not be modified
    • Updated testing guidelines with best practices and focus priorities
  • Tests

    • Expanded coverage for file protection mechanisms and configuration integrity

…ry (#149)

Multi-layered defense against accidental deletion of Ralph's internal files:

- Create lib/file_protection.sh with validate_ralph_integrity() and
  get_integrity_report() for detecting missing critical files
- Tighten ALLOWED_TOOLS default from broad Bash(git *) to enumerated
  safe git subcommands, preventing git clean/git rm/git reset
- Add "Protected Files" section to PROMPT.md templates warning Claude
  to never modify .ralph/ or .ralphrc during cleanup sessions
- Add pre-loop integrity check in ralph_loop.sh that halts execution
  if critical files are missing, with recovery instructions
- Make .ralphrc missing a critical failure in ralph-enable verification

Closes #149
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 17, 2026

Walkthrough

The PR introduces a file protection system for Ralph projects by creating a new validation library, restricting destructive git commands to explicit subcommands, and adding startup and per-loop integrity checks to prevent accidental deletion of critical configuration files (.ralph/, .ralphrc).

Changes

Cohort / File(s) Summary
File Protection Library
lib/file_protection.sh
New module introducing RALPH_REQUIRED_PATHS constant, validate_ralph_integrity() to check file presence, and get_integrity_report() for human-readable integrity diagnostics.
ALLOWED_TOOLS Restrictions
setup.sh, lib/enable_core.sh, templates/ralphrc.template, templates/PROMPT.md, tests/unit/test_cli_parsing.bats, tests/integration/test_project_setup.bats
Replaced broad Bash(git \*) permission with explicit list of safe git subcommands (add, commit, diff, log, status, push, pull, fetch, checkout, branch, stash, merge, tag) to prevent destructive commands.
Core Execution Integration
ralph_loop.sh, ralph_enable.sh
Added startup integrity validation before main loop and per-loop checks at each iteration; upgraded .ralphrc verification in phase_verification from warning to error with descriptive messaging.
Test Coverage Expansion
tests/unit/test_file_protection.bats, tests/unit/test_integrity_check.bats, tests/unit/test_enable_core.bats, tests/unit/test_ralph_enable.bats
Added 155+ lines of comprehensive BATs tests covering integrity check success/failure scenarios, missing file detection, recovery instructions, and protection section ordering.
Documentation Updates
CLAUDE.md, create_files.sh
Enhanced documentation with Protected Files section detailing critical .ralph/ and .ralphrc infrastructure, multi-layer protection strategy, and testing guidelines (20% per loop emphasis).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through Ralph's domain,
"Protect the .ralph, don't cause pain!"
With git commands tamed and constrained,
Integrity checks stand guard, sustained.
No files deleted, all files maintained! 🛡️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.33% 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 and specifically describes the primary change: preventing Ralph from deleting its own .ralph/ directory, which is the main objective of this PR.
Linked Issues check ✅ Passed The PR comprehensively addresses all coding requirements from #149: multi-layered file protection (ALLOWED_TOOLS restrictions, Protected Files warnings, integrity validation), mandatory .ralphrc generation, startup/loop integrity checks, and recovery instructions.
Out of Scope Changes check ✅ Passed All changes are directly scoped to #149 objectives. The test additions, documentation updates, library additions, and configuration changes all support the core goal of preventing Ralph from deleting internal files and ensuring .ralphrc generation.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/issue-149-file-protection

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

@macroscopeapp
Copy link
Contributor

macroscopeapp bot commented Feb 17, 2026

Add integrity checks to halt ralph_loop.sh when .ralph/ or .ralphrc is missing to prevent Ralph from deleting its own .ralph/ directory

Introduce lib/file_protection.sh with validate_ralph_integrity and get_integrity_report; gate startup and each loop iteration in ralph_loop.sh; fail ralph_enable verification if .ralphrc is absent; restrict default ALLOWED_TOOLS and update prompts/templates to warn against modifying protected files.

📍Where to Start

Start with the integrity gate in the main function of ralph_loop.sh, then review validate_ralph_integrity and get_integrity_report in lib/file_protection.sh.


Macroscope summarized 7c01c73.

@github-actions
Copy link

Code Review: feat(protection): prevent Ralph from deleting its own .ralph/ directory

This is a well-implemented multi-layered defense strategy. Overall, the code quality is excellent with comprehensive test coverage.

✅ Strengths

1. Multi-Layered Defense Design
The three-layer approach is well-thought-out:

  • Layer 1: restriction prevents destructive git commands (, , )
  • Layer 2: PROMPT.md warning provides clear guidance to Claude
  • Layer 3: Pre-loop integrity check halts execution with recovery instructions

This defense-in-depth approach significantly reduces the likelihood of accidental file deletion.

2. Clean Architecture

  • New lib/file_protection.sh module is well-organized with clear function contracts
  • RALPH_REQUIRED_PATHS array is explicitly documented
  • Functions have descriptive names and proper comments
  • Good separation of concerns - validation logic isolated from loop logic

3. Comprehensive Test Coverage

  • 22 tests in test_file_protection.bats for the new module
  • 12 tests in test_integrity_check.bats for integration behavior
  • Updated existing tests for ALLOWED_TOOLS changes
  • Tests cover success paths, failure paths, and edge cases

4. Documentation

5. Proper Error Handling

  • Integrity check at startup (ralph_loop.sh:1481) exits immediately with clear error
  • In-loop check (ralph_loop.sh:1506) calls reset_session and breaks cleanly
  • get_integrity_report() provides human-readable output with recovery steps

⚠️ Minor Considerations

1. Edge Case: reset_session with Missing .ralph/ Directory

If the integrity check fails mid-loop (Line 1506), reset_session is called. This function writes to .ralph/.ralph_session (Line 791) and .ralph/.exit_signals (Line 811). If .ralph/ has been deleted, these writes will fail, causing the script to exit under set -e before reaching the update_status call or break statement.

Impact: Low - this is an edge case that only occurs if Claude somehow deletes .ralph/ during execution, which the protection layers are designed to prevent.

Suggestion (optional improvement):

# In reset_session(), after line 785:
# Ensure .ralph/ directory exists (in case it was deleted mid-loop)
mkdir -p "$RALPH_DIR" 2>/dev/null || true

2. ALLOWED_TOOLS Trade-off

The switch from Bash(git *) to enumerated subcommands prevents destructive operations but also limits legitimate refactoring workflows (e.g., git clean -fdx for build artifacts outside .ralph/).

Assessment: This is an acceptable trade-off. Users can override in .ralphrc if needed, and the default is safer. The comment in ralphrc.template (Line 40-41) provides clear guidance.

3. Test Isolation in test_ralph_enable.bats

The verification tests (lines 272-333) extract phase_verification from ralph_enable.sh using sed and run it in a subshell. This works but is somewhat fragile.

Assessment: The tests are passing, so this is not a blocker. An alternative would be to add a "--verify-only" flag to ralph_enable_ci.sh for testing purposes.

📋 Verification

  • Code follows bash best practices
  • Functions are well-documented with comments
  • Test coverage is comprehensive (548 tests passing, 38 new)
  • Error messages are clear and actionable
  • Security considerations are addressed
  • Documentation is updated (CLAUDE.md, templates)

💡 Recommendation

Approve with optional follow-up for the minor reset_session edge case improvement. The implementation is production-ready as-is, and the edge case is unlikely to occur in practice given the multi-layered protection.

Excellent work on addressing Issue #149!

@github-actions
Copy link

Code Review: feat(protection): prevent Ralph from deleting its own .ralph/ directory

This is a well-implemented multi-layered defense strategy. Overall, the code quality is excellent with comprehensive test coverage.

✅ Strengths

1. Multi-Layered Defense Design
The three-layer approach is well-thought-out:

  • Layer 1: ALLOWED_TOOLS restriction prevents destructive git commands (git clean, git rm, git reset)
  • Layer 2: PROMPT.md warning provides clear guidance to Claude
  • Layer 3: Pre-loop integrity check halts execution with recovery instructions

This defense-in-depth approach significantly reduces the likelihood of accidental file deletion.

2. Clean Architecture

  • New lib/file_protection.sh module is well-organized with clear function contracts
  • RALPH_REQUIRED_PATHS array is explicitly documented
  • Functions have descriptive names and proper comments
  • Good separation of concerns - validation logic isolated from loop logic

3. Comprehensive Test Coverage

  • 22 tests in test_file_protection.bats for the new module
  • 12 tests in test_integrity_check.bats for integration behavior
  • Updated existing tests for ALLOWED_TOOLS changes
  • Tests cover success paths, failure paths, and edge cases

4. Documentation

5. Proper Error Handling

  • Integrity check at startup (ralph_loop.sh:1481) exits immediately with clear error
  • In-loop check (ralph_loop.sh:1506) calls reset_session and breaks cleanly
  • get_integrity_report() provides human-readable output with recovery steps

⚠️ Minor Considerations

1. Edge Case: reset_session with Missing .ralph/ Directory

If the integrity check fails mid-loop (Line 1506), reset_session is called. This function writes to .ralph/.ralph_session (Line 791) and .ralph/.exit_signals (Line 811). If .ralph/ has been deleted, these writes will fail, causing the script to exit under set -e before reaching the update_status call or break statement.

Impact: Low - this is an edge case that only occurs if Claude somehow deletes .ralph/ during execution, which the protection layers are designed to prevent.

Suggestion (optional improvement): Ensure .ralph/ directory exists in reset_session() if it was deleted mid-loop.

2. ALLOWED_TOOLS Trade-off

The switch from Bash(git *) to enumerated subcommands prevents destructive operations but also limits legitimate refactoring workflows (e.g., git clean -fdx for build artifacts outside .ralph/).

Assessment: This is an acceptable trade-off. Users can override in .ralphrc if needed, and the default is safer.

📋 Verification

  • Code follows bash best practices
  • Functions are well-documented with comments
  • Test coverage is comprehensive (548 tests passing, 38 new)
  • Error messages are clear and actionable
  • Security considerations are addressed
  • Documentation is updated (CLAUDE.md, templates)

💡 Recommendation

Approve - The implementation is production-ready. The edge case in reset_session is unlikely to occur in practice given the multi-layered protection.

Excellent work on addressing Issue #149!

@github-actions
Copy link

Review comment posted successfully: #182 (comment)

New%20session%20-%202026-02-17T22%3A25%3A45.167Z
opencode session  |  github run

@github-actions
Copy link

PR Review: feat(protection) - prevent Ralph from deleting its own .ralph/ directory

Good overall implementation of the defense-in-depth strategy described in #149. The three-layer approach (ALLOWED_TOOLS restriction, PROMPT.md warning, pre-loop integrity check) is well-reasoned, and the new lib/file_protection.sh module is clean and focused.

Strengths

  • Correct scope for validation: Only the 5 files truly critical to loop operation are required. Optional state files (.call_count, status.json, etc.) are excluded from validation — this avoids false positives on freshly initialized projects.
  • Clean module design: file_protection.sh at 57 lines is intentionally lightweight. The separation between validate_ralph_integrity() (returns exit code) and get_integrity_report() (returns human text) is a good interface boundary.
  • Correct recovery path: Halting the loop with break (in-loop failure) vs. exit 1 (startup failure) is the right distinction — in-loop failure leaves state intact for inspection, startup failure prevents any execution at all.
  • Test coverage: 38 new tests covering both the library functions and their integration into the loop. The tests are well-structured with clear helper functions and grouped by behavior.

Issues to Consider

1. validate_ralph_integrity() does not respect RALPH_DIR

The required paths are hardcoded as relative paths (.ralph, .ralph/PROMPT.md, etc.), but the running loop configures RALPH_DIR which defaults to .ralph and is loaded from .ralphrc. If a project ever uses a non-default RALPH_DIR, the integrity check will always fail because it checks literal .ralph/ instead of $RALPH_DIR/. This is likely an edge case today, but worth a TODO comment at minimum.

2. export -f in file_protection.sh is unnecessary for primary use case

Exporting functions with export -f is only needed when functions must survive into subshells spawned by bash -c or similar. Since ralph_loop.sh sources the file directly (same shell context), the exports are not needed for production operation. They do help in tests when run validate_ralph_integrity is called (bats run forks a subshell), so they are not harmful — but worth noting they are serving the test harness, not the production path.

3. Test fragility: phase_verification extraction via sed

In test_ralph_enable.bats, two new tests extract phase_verification() using:

sed -n "/^phase_verification()/,/^}/p"

This sed range stops at the first line matching ^} at column 1, which will truncate the function body if it ever contains top-level closing braces before the final one. phase_verification already contains if/fi blocks and is 50+ lines — it works today, but is fragile. A more robust approach would call ralph-enable-ci in a controlled test directory rather than extracting the function. This is a test fragility concern, not a production bug.

4. ALLOWED_TOOLS comment redundancy in ralphrc.template

The template has three comment lines before the ALLOWED_TOOLS assignment that overlap in content:

# NOTE: Default uses specific git subcommands for safety. To allow ALL git commands: Bash(git *)
# Dangerous commands excluded by default: git clean, git rm, git reset (can delete .ralph/)
# Safe git subcommands only - broad Bash(git *) allows destructive commands like git clean/git rm (Issue #149)

Three comment lines for one config entry is slightly over-explained — the third repeats the first two. Consolidating to two lines (user-facing note + specific dangerous commands) would improve readability in user-edited files.

Minor Points

  • create_files.sh adds the Protected Files section to the inline heredoc template, matching lib/enable_core.sh and templates/PROMPT.md. Consistent — good.
  • The test_integrity_check.bats test that asserts the startup check comes before while true is a useful structural regression guard.
  • The ralph_enable.sh change from print_warning to print_error with all_good=false for missing .ralphrc is the right severity escalation.
  • In-loop check placement (before should_halt_execution) and startup check placement (before init_session_tracking) are both correctly ordered.

Summary

The implementation is sound and the multi-layer strategy is well-executed. Two items are worth addressing before merge:

  1. The RALPH_DIR vs. hardcoded paths gap in validate_ralph_integrity() — functional correctness concern for non-default configurations
  2. The sed-based function extraction in test_ralph_enable.bats — test fragility that could produce silent false-positives as the function grows

The comment redundancy in ralphrc.template is cosmetic and can be addressed any time.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
ralph_loop.sh (1)

259-261: ⚠️ Potential issue | 🟠 Major

Inconsistent ALLOWED_TOOLS default in tmux parameter forwarding.

The comparison uses the old default "Write,Read,Edit,Bash(git *),Bash(npm *),Bash(pytest)" instead of the new granular git subcommands. This will cause --allowed-tools to always be forwarded in --monitor mode even when using defaults, as the comparison will never match.

🔧 Proposed fix
     # Forward --allowed-tools if non-default
-    if [[ "$CLAUDE_ALLOWED_TOOLS" != "Write,Read,Edit,Bash(git *),Bash(npm *),Bash(pytest)" ]]; then
+    if [[ "$CLAUDE_ALLOWED_TOOLS" != "Write,Read,Edit,Bash(git add *),Bash(git commit *),Bash(git diff *),Bash(git log *),Bash(git status),Bash(git status *),Bash(git push *),Bash(git pull *),Bash(git fetch *),Bash(git checkout *),Bash(git branch *),Bash(git stash *),Bash(git merge *),Bash(git tag *),Bash(npm *),Bash(pytest)" ]]; then
         ralph_cmd="$ralph_cmd --allowed-tools '$CLAUDE_ALLOWED_TOOLS'"
     fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ralph_loop.sh` around lines 259 - 261, The conditional comparing
CLAUDE_ALLOWED_TOOLS uses the old default string so it never matches and forces
forwarding; update the comparison in the if that mutates ralph_cmd to use the
new canonical default value for CLAUDE_ALLOWED_TOOLS (the same granular
git-subcommand string used when the default is defined/initialized) so the
equality check succeeds when defaults are in use; ensure you reference the same
exact string formatting/quoting as the place where CLAUDE_ALLOWED_TOOLS is set
and keep the branch that appends to ralph_cmd unchanged (the symbols to edit are
CLAUDE_ALLOWED_TOOLS and ralph_cmd).
🧹 Nitpick comments (1)
tests/unit/test_integrity_check.bats (1)

38-45: Consider adding .ralphrc configuration content.

The helper creates .ralphrc with minimal content (RALPH_DIR=.ralph). For completeness, consider adding at least ALLOWED_TOOLS to match typical project setup, though this is minor since the integrity check focuses on file existence.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/test_integrity_check.bats` around lines 38 - 45, The helper
function create_integrity_project writes a minimal .ralphrc containing only
RALPH_DIR; update this helper to include a more complete configuration by adding
an ALLOWED_TOOLS entry in the .ralphrc content so tests mimic a typical project
setup—locate create_integrity_project in tests/unit/test_integrity_check.bats
and append an ALLOWED_TOOLS line (e.g.,
ALLOWED_TOOLS=<comma-separated-or-empty>) alongside the existing
RALPH_DIR=.ralph to ensure the integrity checks run against a representative
config file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CLAUDE.md`:
- Around line 486-494: The markdown table titled "Required vs Optional Files" is
missing blank lines before and after it (triggers markdownlint MD058); edit the
CLAUDE.md section containing the "Required vs Optional Files" table and add one
blank line above the table header and one blank line after the table block so
there is an empty line separating the table from surrounding text.
- Around line 472-475: Update the Modern CLI configuration example in CLAUDE.md
so it matches the new granular default CLAUDE_ALLOWED_TOOLS (e.g., use Bash(git
add *), Bash(git commit *), etc.) or add a short clarifying note next to the
example that the documentation now defaults to granular git subcommands and
users can still override in .ralphrc; ensure references to "File Protection" and
the ALLOWED_TOOLS example are consistent and mention CLAUDE_ALLOWED_TOOLS by
name so readers can reconcile the example with the implementation.

In `@lib/file_protection.sh`:
- Around line 40-52: In get_integrity_report(), stop using raw echo and switch
to the wizard_utils helpers: when RALPH_MISSING_FILES is empty call print_info
to state "All required Ralph files are intact."; when there are missing files
use print_error for the summary line ("Ralph integrity check failed. Missing
files:"), use print_error for each missing path from the RALPH_MISSING_FILES
array, and emit the restoration hint via print_info or print_warning (e.g., "To
restore, run: ralph-enable --force"); keep the function return behavior the
same.
- Around line 22-34: In validate_ralph_integrity, avoid leaking globals by
declaring the loop variable and the missing-files array as local: add "local
path" before the for loop and change RALPH_MISSING_FILES to a function-local
variable (e.g., "local ralph_missing_files=()") and update all uses in that
function (checks and appends) to ralph_missing_files; keep RALPH_REQUIRED_PATHS
as the external constant. This confines state to validate_ralph_integrity and
prevents the global "path" and missing-files array from being clobbered when the
library is sourced.

In `@tests/unit/test_enable_core.bats`:
- Around line 396-421: Add a short strategy comment above the two tests for
generate_prompt_md explaining that these tests verify the presence and ordering
of the "Protected Files" section so the protection warning (e.g., entries like
".ralph/" and ".ralphrc" and the phrase "NEVER delete") is displayed before the
"Testing Guidelines" section; reference generate_prompt_md and the strings
"Protected Files" and "Testing Guidelines" in the comment, keep it brief (one to
two sentences) and aligned with the tests' intent to satisfy the tests/**/*.bats
comment requirement.

---

Outside diff comments:
In `@ralph_loop.sh`:
- Around line 259-261: The conditional comparing CLAUDE_ALLOWED_TOOLS uses the
old default string so it never matches and forces forwarding; update the
comparison in the if that mutates ralph_cmd to use the new canonical default
value for CLAUDE_ALLOWED_TOOLS (the same granular git-subcommand string used
when the default is defined/initialized) so the equality check succeeds when
defaults are in use; ensure you reference the same exact string
formatting/quoting as the place where CLAUDE_ALLOWED_TOOLS is set and keep the
branch that appends to ralph_cmd unchanged (the symbols to edit are
CLAUDE_ALLOWED_TOOLS and ralph_cmd).

---

Nitpick comments:
In `@tests/unit/test_integrity_check.bats`:
- Around line 38-45: The helper function create_integrity_project writes a
minimal .ralphrc containing only RALPH_DIR; update this helper to include a more
complete configuration by adding an ALLOWED_TOOLS entry in the .ralphrc content
so tests mimic a typical project setup—locate create_integrity_project in
tests/unit/test_integrity_check.bats and append an ALLOWED_TOOLS line (e.g.,
ALLOWED_TOOLS=<comma-separated-or-empty>) alongside the existing
RALPH_DIR=.ralph to ensure the integrity checks run against a representative
config file.

Comment on lines +472 to +475
**Layer 1: ALLOWED_TOOLS Restriction**
- The default `CLAUDE_ALLOWED_TOOLS` uses granular `Bash(git add *)`, `Bash(git commit *)` etc. instead of `Bash(git *)`, preventing `git clean`, `git rm`, and other destructive git commands
- Users can override in `.ralphrc` but the defaults are safe

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Align the ALLOWED_TOOLS example with the new granular defaults.
The File Protection section states defaults now use granular git subcommands, but the earlier Modern CLI configuration example still shows Bash(git *), which is now misleading. Please update the example or add a note clarifying the difference.

Based on learnings Keep CLAUDE.md documentation synchronized with implementation changes: update script documentation, implementation sections, README updates, and template maintenance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLAUDE.md` around lines 472 - 475, Update the Modern CLI configuration
example in CLAUDE.md so it matches the new granular default CLAUDE_ALLOWED_TOOLS
(e.g., use Bash(git add *), Bash(git commit *), etc.) or add a short clarifying
note next to the example that the documentation now defaults to granular git
subcommands and users can still override in .ralphrc; ensure references to "File
Protection" and the ALLOWED_TOOLS example are consistent and mention
CLAUDE_ALLOWED_TOOLS by name so readers can reconcile the example with the
implementation.

Comment on lines +486 to +494
**Required vs Optional Files:**
| Required (validation fails) | Optional (no validation) |
|---|---|
| `.ralph/` directory | `.ralph/logs/` |
| `.ralph/PROMPT.md` | `.ralph/status.json` |
| `.ralph/fix_plan.md` | `.ralph/.call_count` |
| `.ralph/AGENT.md` | `.ralph/.exit_signals` |
| `.ralphrc` | `.ralph/.circuit_breaker_state` |

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add blank lines around the Required vs Optional Files table.
markdownlint MD058 expects a blank line before and after tables.

🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 487-487: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLAUDE.md` around lines 486 - 494, The markdown table titled "Required vs
Optional Files" is missing blank lines before and after it (triggers
markdownlint MD058); edit the CLAUDE.md section containing the "Required vs
Optional Files" table and add one blank line above the table header and one
blank line after the table block so there is an empty line separating the table
from surrounding text.

Comment on lines +22 to +34
validate_ralph_integrity() {
RALPH_MISSING_FILES=()

for path in "${RALPH_REQUIRED_PATHS[@]}"; do
if [[ ! -e "$path" ]]; then
RALPH_MISSING_FILES+=("$path")
fi
done

if [[ ${#RALPH_MISSING_FILES[@]} -gt 0 ]]; then
return 1
fi
return 0
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Declare the loop variable as local to avoid global leakage.
Bash will otherwise promote path to a global, which can create hard‑to‑trace side effects when this library is sourced.

🛠️ Suggested fix
 validate_ralph_integrity() {
+    local path
     RALPH_MISSING_FILES=()

     for path in "${RALPH_REQUIRED_PATHS[@]}"; do
         if [[ ! -e "$path" ]]; then

Based on learnings Bash script naming conventions: use snake_case for function names and local variables (e.g., get_tmux_base_index, base_win) and UPPER_SNAKE_CASE for constants (e.g., RALPH_DIR, PROMPT_FILE). Apply to all .sh files in the project; ensure local variables inside functions are declared with the local keyword and maintain consistent casing across the codebase.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
validate_ralph_integrity() {
RALPH_MISSING_FILES=()
for path in "${RALPH_REQUIRED_PATHS[@]}"; do
if [[ ! -e "$path" ]]; then
RALPH_MISSING_FILES+=("$path")
fi
done
if [[ ${#RALPH_MISSING_FILES[@]} -gt 0 ]]; then
return 1
fi
return 0
validate_ralph_integrity() {
local path
RALPH_MISSING_FILES=()
for path in "${RALPH_REQUIRED_PATHS[@]}"; do
if [[ ! -e "$path" ]]; then
RALPH_MISSING_FILES+=("$path")
fi
done
if [[ ${`#RALPH_MISSING_FILES`[@]} -gt 0 ]]; then
return 1
fi
return 0
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/file_protection.sh` around lines 22 - 34, In validate_ralph_integrity,
avoid leaking globals by declaring the loop variable and the missing-files array
as local: add "local path" before the for loop and change RALPH_MISSING_FILES to
a function-local variable (e.g., "local ralph_missing_files=()") and update all
uses in that function (checks and appends) to ralph_missing_files; keep
RALPH_REQUIRED_PATHS as the external constant. This confines state to
validate_ralph_integrity and prevents the global "path" and missing-files array
from being clobbered when the library is sourced.

Comment on lines +40 to +52
get_integrity_report() {
if [[ ${#RALPH_MISSING_FILES[@]} -eq 0 ]]; then
echo "All required Ralph files are intact."
return 0
fi

echo "Ralph integrity check failed. Missing files:"
for path in "${RALPH_MISSING_FILES[@]}"; do
echo " - $path"
done
echo ""
echo "To restore, run: ralph-enable --force"
return 0
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use wizard_utils output helpers for error/report messaging.
The integrity report currently uses raw echo. For consistency and per guidelines, emit the failure line with print_error() and the recovery hint with print_info() (or print_warning()), assuming wizard_utils is sourced by the caller.

💡 Example adjustment
 get_integrity_report() {
     if [[ ${`#RALPH_MISSING_FILES`[@]} -eq 0 ]]; then
-        echo "All required Ralph files are intact."
+        print_success "All required Ralph files are intact."
         return 0
     fi

-    echo "Ralph integrity check failed. Missing files:"
+    print_error "Ralph integrity check failed. Missing files:"
     for path in "${RALPH_MISSING_FILES[@]}"; do
-        echo "  - $path"
+        print_bullet "$path"
     done
-    echo ""
-    echo "To restore, run: ralph-enable --force"
+    print_info "To restore, run: ralph-enable --force"
     return 0
 }

As per coding guidelines: All bash functions must handle errors gracefully with meaningful error messages using print_error() from lib/wizard_utils.sh.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
get_integrity_report() {
if [[ ${#RALPH_MISSING_FILES[@]} -eq 0 ]]; then
echo "All required Ralph files are intact."
return 0
fi
echo "Ralph integrity check failed. Missing files:"
for path in "${RALPH_MISSING_FILES[@]}"; do
echo " - $path"
done
echo ""
echo "To restore, run: ralph-enable --force"
return 0
get_integrity_report() {
if [[ ${`#RALPH_MISSING_FILES`[@]} -eq 0 ]]; then
print_success "All required Ralph files are intact."
return 0
fi
print_error "Ralph integrity check failed. Missing files:"
for path in "${RALPH_MISSING_FILES[@]}"; do
print_bullet "$path"
done
print_info "To restore, run: ralph-enable --force"
return 0
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/file_protection.sh` around lines 40 - 52, In get_integrity_report(), stop
using raw echo and switch to the wizard_utils helpers: when RALPH_MISSING_FILES
is empty call print_info to state "All required Ralph files are intact."; when
there are missing files use print_error for the summary line ("Ralph integrity
check failed. Missing files:"), use print_error for each missing path from the
RALPH_MISSING_FILES array, and emit the restoration hint via print_info or
print_warning (e.g., "To restore, run: ralph-enable --force"); keep the function
return behavior the same.

- Fix tmux parameter forwarding using old ALLOWED_TOOLS default (CodeRabbit)
- Add mkdir -p before log_status when .ralph/ deleted mid-loop (Macroscope)
- Update CLAUDE.md CLI example to reference new granular defaults (CodeRabbit)
- Add blank line before Required vs Optional Files table (CodeRabbit MD058)
- Declare loop variable as local in validate_ralph_integrity (CodeRabbit)
- Add strategy comment for Protected Files tests (CodeRabbit)
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
ralph_loop.sh (1)

259-261: Consider extracting the default tools string to a constant.

The same long string appears on line 57 (default assignment) and line 260 (comparison). If these ever drift apart, tmux parameter forwarding will silently break. A shared constant would eliminate this risk.

♻️ Proposed refactor to avoid string duplication
+# Default allowed tools - granular git subcommands only (Issue `#149`)
+DEFAULT_ALLOWED_TOOLS="Write,Read,Edit,Bash(git add *),Bash(git commit *),Bash(git diff *),Bash(git log *),Bash(git status),Bash(git status *),Bash(git push *),Bash(git pull *),Bash(git fetch *),Bash(git checkout *),Bash(git branch *),Bash(git stash *),Bash(git merge *),Bash(git tag *),Bash(npm *),Bash(pytest)"

 # Safe git subcommands only - broad Bash(git *) allows destructive commands like git clean/git rm (Issue `#149`)
-CLAUDE_ALLOWED_TOOLS="${CLAUDE_ALLOWED_TOOLS:-Write,Read,Edit,Bash(git add *),Bash(git commit *),Bash(git diff *),Bash(git log *),Bash(git status),Bash(git status *),Bash(git push *),Bash(git pull *),Bash(git fetch *),Bash(git checkout *),Bash(git branch *),Bash(git stash *),Bash(git merge *),Bash(git tag *),Bash(npm *),Bash(pytest)}"
+CLAUDE_ALLOWED_TOOLS="${CLAUDE_ALLOWED_TOOLS:-$DEFAULT_ALLOWED_TOOLS}"

Then at line 260:

     # Safe git subcommands only - broad Bash(git *) allows destructive commands like git clean/git rm (Issue `#149`)
-    if [[ "$CLAUDE_ALLOWED_TOOLS" != "Write,Read,Edit,Bash(git add *),Bash(git commit *),Bash(git diff *),Bash(git log *),Bash(git status),Bash(git status *),Bash(git push *),Bash(git pull *),Bash(git fetch *),Bash(git checkout *),Bash(git branch *),Bash(git stash *),Bash(git merge *),Bash(git tag *),Bash(npm *),Bash(pytest)" ]]; then
+    if [[ "$CLAUDE_ALLOWED_TOOLS" != "$DEFAULT_ALLOWED_TOOLS" ]]; then
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ralph_loop.sh` around lines 259 - 261, Extract the long default allowed-tools
string into a single constant (e.g., DEFAULT_ALLOWED_TOOLS) and use that
constant wherever the literal is currently used: replace the default assignment
of CLAUDE_ALLOWED_TOOLS (around the earlier default) to reference
DEFAULT_ALLOWED_TOOLS and update the conditional comparison at the ralph_cmd
construction (the if [[ "$CLAUDE_ALLOWED_TOOLS" != "..." ]]) to compare against
DEFAULT_ALLOWED_TOOLS; ensure ralph_cmd uses the same constant value when
appending --allowed-tools so both locations share one source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@ralph_loop.sh`:
- Around line 259-261: Extract the long default allowed-tools string into a
single constant (e.g., DEFAULT_ALLOWED_TOOLS) and use that constant wherever the
literal is currently used: replace the default assignment of
CLAUDE_ALLOWED_TOOLS (around the earlier default) to reference
DEFAULT_ALLOWED_TOOLS and update the conditional comparison at the ralph_cmd
construction (the if [[ "$CLAUDE_ALLOWED_TOOLS" != "..." ]]) to compare against
DEFAULT_ALLOWED_TOOLS; ensure ralph_cmd uses the same constant value when
appending --allowed-tools so both locations share one source of truth.

@github-actions
Copy link

Review: feat(protection): prevent Ralph from deleting its own .ralph/ directory\n\nThis PR implements a solid multi-layered defense strategy for Issue #149. The overall architecture is well-thought-out and the test coverage is comprehensive. Here are my observations after reviewing the changes in context of the prior CodeRabbit feedback.\n\n---\n\n### Critical Issue: ALLOWED_TOOLS Comparison String in setup_tmux_session()\n\nCodeRabbit flagged this and it deserves emphasis. In ralph_loop.sh, the setup_tmux_session() function compares the current CLAUDE_ALLOWED_TOOLS value against a hardcoded default to decide whether to forward --allowed-tools. The diff shows this comparison string was updated, which is good - but it's worth confirming the exact string matches the new canonical default byte-for-byte. A single character difference (spacing, ordering, trailing entry) would mean --allowed-tools gets forwarded on every --monitor run even when using defaults.\n\nRecommendation: Consider extracting the canonical default into a named constant (e.g., RALPH_DEFAULT_ALLOWED_TOOLS) defined once, then referencing it in both the assignment and the comparison. This eliminates the risk of the two strings drifting apart again in the future.\n\n---\n\n### lib/file_protection.sh: Global State Concern\n\nThe validate_ralph_integrity() function populates RALPH_MISSING_FILES as a module-level global array. While the path loop variable is not declared local - it persists between calls. This is intentional (so get_integrity_report() can read it), but it does mean:\n\n1. A caller that checks RALPH_MISSING_FILES before calling validate_ralph_integrity() will see stale data.\n2. Any external code that sources file_protection.sh inherits RALPH_MISSING_FILES in its global namespace.\n\nThe module comment documents this correctly, so this is acceptable as-is. However, note that currently only the functions are exported (export -f), not the array - this could cause get_integrity_report() to fail when called in a subprocess context if RALPH_MISSING_FILES is populated in a parent shell.\n\n---\n\n### get_integrity_report(): Subshell Usage\n\nCodeRabbit suggested using wizard_utils helpers. I agree directionally, but file_protection.sh is sourced into ralph_loop.sh, which may not always have wizard_utils.sh loaded. The raw echo approach is safer for portability. One minor point: ralph_loop.sh calls echo with command substitution of get_integrity_report rather than calling get_integrity_report directly - the command substitution creates a subshell and strips trailing newlines. A direct call would work equally well and be slightly cleaner.\n\n---\n\n### ralph_enable.sh: .ralphrc Promoted to Critical\n\nThe change from optional warning to critical failure for missing .ralphrc is the right call given that .ralphrc is now in RALPH_REQUIRED_PATHS. The consistency between ralph_enable.sh's verification and file_protection.sh's required paths list is important - if one changes, the other must follow. Consider adding a comment near both locations cross-referencing each other, so future maintainers know they must stay in sync.\n\n---\n\n### Test Coverage\n\nThe test suite additions are well-structured:\n\n- test_file_protection.bats (22 tests) - good coverage of both success and multi-failure scenarios for validate_ralph_integrity, and the get_integrity_report output format\n- test_integrity_check.bats (12 tests) - correctly tests both startup and in-loop placement\n\nOne observation: the create_integrity_project test helper (noted by CodeRabbit) creates a minimal .ralphrc with only RALPH_DIR=.ralph. A more representative fixture with ALLOWED_TOOLS set would better simulate real projects and could catch edge cases in future refactors of .ralphrc parsing.\n\n---\n\n### ALLOWED_TOOLS List: Missing git rebase\n\nThe new safe subcommand list includes git merge and git stash but omits git rebase. For projects that use rebase-based workflows, Claude would be blocked. This may be intentional (rebase is more destructive than merge), but it's worth documenting explicitly in templates/ralphrc.template so users know what to add if they need it.\n\n---\n\n### CLAUDE.md Table Formatting\n\nCodeRabbit noted the Required vs Optional Files table needs blank lines before and after to pass markdownlint MD058. This is a minor formatting issue worth a quick fix.\n\n---\n\n### Summary\n\n| Area | Assessment |\n|------|------------|\n| Architecture (3-layer defense) | Solid |\n| lib/file_protection.sh logic | Correct, minor export concern |\n| ALLOWED_TOOLS tightening | Good, verify comparison string is byte-exact |\n| .ralphrc as critical failure | Right call |\n| Test coverage (548 tests) | Comprehensive |\n| Documentation | Good, minor formatting fix needed |\n\nThe core implementation is sound. The main items to address before merging are: (1) verify or refactor the ALLOWED_TOOLS comparison string to eliminate drift risk, and (2) apply the CLAUDE.md table formatting fix. Everything else is either a nitpick or already addressed.

@frankbria frankbria merged commit f6cae65 into main Feb 17, 2026
7 of 8 checks passed
@frankbria frankbria deleted the feature/issue-149-file-protection branch February 17, 2026 22:56
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.

Bug: ralph deletes its own .ralph/ directory during cleanup + .ralphrc not generated

1 participant