Skip to content

Circuit breaker false positive: progress detection is blind when the loop CWD is not a git repository (multi-repo workspace) #340

Description

@DrumRobot

Description

lib/response_analyzer.sh detects loop progress exclusively through git: it runs git rev-parse --git-dir from the loop's working directory and, only if that succeeds, counts changed files via git diff (JSON path around response_analyzer.sh:568-602, text path around :763-796 at e8533cc).

When Ralph supervises a multi-repo workspace — a root folder that contains several sub-repositories plus .ralph/, where the root itself is not a git repository — the git rev-parse gate fails every loop, so the entire file-change check is skipped. The Claude CLI JSON result contains no files_modified / metadata.files_changed field either, so files_modified stays 0 and has_progress stays false on every loop, regardless of how much real work was done.

Work that is invisible to the analyzer in this layout includes:

  • commits made in sub-repositories via git -C <sub-repo> (encouraged by the compound-command guidance, since cd is unavailable)
  • GitHub-side work (gh pr comment, review consolidation, issue updates) that changes no local file
  • .ralph/fix_plan.md tracker updates (even when .ralph/ is itself a git repository — git rev-parse from the parent CWD searches upward, not downward)

The result: consecutive_no_progress increments unconditionally and the circuit breaker opens after the threshold, no matter what the agent actually accomplished. The operator's only recourse is repeated manual ralph --reset-circuit, or raising CB_NO_PROGRESS_THRESHOLD so high that the no-progress breaker is effectively disabled — both of which discard the stagnation signal entirely.

This is the non-git-CWD sibling of #141 ("no progress when Claude commits within the loop"). The #141 fix added committed-changes detection, but it still assumes the loop CWD is inside a git repository.

Current Behavior

In a non-git multi-repo workspace root:

  1. A loop completes genuine work (e.g. consolidates a PR review, posts the review summary, updates .ralph/fix_plan.md, syncs a RAG store) and exits with subtype: success, no permission denials.
  2. The analyzer still records has_progress: false, files_modified: 0.
  3. consecutive_no_progress reaches the threshold and the breaker opens: "No recovery, opening circuit after N loops".
  4. Every subsequent run repeats this, so the breaker re-opens perpetually.

Expected Behavior

Progress detection should have at least one signal path that works when the loop CWD is not a git repository.

Possible directions (any one of these would fix the class of false positives):

  1. Fall back to the .ralph/ directory's own repository: if git -C "$RALPH_DIR" rev-parse succeeds, diff there too — tracker updates (fix_plan.md) are a reasonable progress proxy.
  2. Trust an explicit agent self-report: the analyzer already extracts EXIT_SIGNAL from a RALPH_STATUS block embedded in the .result text. A FILES_MODIFIED: N (or PROGRESS: true) line in the same block could set has_progress with the same explicit-intent semantics.
  3. Configurable progress roots: e.g. RALPH_PROGRESS_GIT_DIRS="sub-repo-a sub-repo-b .ralph" in .ralphrc; the analyzer iterates them with git -C.

Workaround (if any)

Raising CB_NO_PROGRESS_THRESHOLD to a very high value in .ralphrc — which effectively disables no-progress stagnation detection rather than fixing it.

Environment

  • ralph-claude-code: e8533cc (main)
  • CLAUDE_OUTPUT_FORMAT="json", PROJECT_TYPE="multi"
  • macOS (bash loop, not SDK)
  • Workspace layout: non-git root folder containing multiple sub-repositories + .ralph/

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions