Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ CLAUDE_ALLOWED_TOOLS="Write,Read,Edit,Bash(git add *),Bash(git commit *),...,Bas
CLAUDE_USE_CONTINUE=true # Enable session continuity
CLAUDE_MIN_VERSION="2.0.76" # Minimum Claude CLI version
CLAUDE_AUTO_UPDATE=true # Auto-update Claude CLI at startup (set false for air-gapped environments)
CLAUDE_MODEL="" # Model override (e.g. claude-sonnet-4-6); empty = CLI default (Issue #228)
CLAUDE_EFFORT="" # Effort level override (e.g. high, low); empty = CLI default (Issue #228)
```

**Auto-Update Configuration:**
Expand All @@ -241,6 +243,11 @@ CLAUDE_AUTO_UPDATE=true # Auto-update Claude CLI at startup (set f
- Both functions use `compare_semver()` for proper major→minor→patch sequential comparison (safe for any patch number, unlike integer arithmetic)
- Environment variable `CLAUDE_CODE_CMD` takes precedence over `.ralphrc`

**Model and Effort Overrides (Issue #228):**
- `CLAUDE_MODEL` sets the `--model` flag on every Claude invocation (e.g., `CLAUDE_MODEL=claude-sonnet-4-6`). Leave empty to use the CLI's default model.
- `CLAUDE_EFFORT` sets the `--effort` flag (e.g., `CLAUDE_EFFORT=high` or `CLAUDE_EFFORT=low`). Leave empty to use the CLI's default.
- Both variables can be set in `.ralphrc` or as environment variables. The environment variable takes precedence over `.ralphrc`.

**CLI Options:**
- `--output-format json|text` - Set Claude output format (default: json). Note: `--live` mode requires JSON and will auto-switch from text to json.
- `--allowed-tools "Write,Read,Bash(git *)"` - Restrict allowed tools
Expand Down
18 changes: 17 additions & 1 deletion ralph_loop.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ LOG_DIR="$RALPH_DIR/logs"
DOCS_DIR="$RALPH_DIR/docs/generated"
STATUS_FILE="$RALPH_DIR/status.json"
PROGRESS_FILE="$RALPH_DIR/progress.json"
CLAUDE_CODE_CMD="claude"
SLEEP_DURATION=3600 # 1 hour in seconds
LIVE_OUTPUT=false # Show Claude Code output in real-time (streaming)
LIVE_LOG_FILE="$RALPH_DIR/live.log" # Fixed file for live output monitoring
Expand All @@ -45,6 +44,8 @@ _env_CB_COOLDOWN_MINUTES="${CB_COOLDOWN_MINUTES:-}"
_env_CB_AUTO_RESET="${CB_AUTO_RESET:-}"
_env_CLAUDE_CODE_CMD="${CLAUDE_CODE_CMD:-}"
_env_CLAUDE_AUTO_UPDATE="${CLAUDE_AUTO_UPDATE:-}"
_env_CLAUDE_MODEL="${CLAUDE_MODEL:-}"
_env_CLAUDE_EFFORT="${CLAUDE_EFFORT:-}"

# Now set defaults (only if not already set by environment)
MAX_CALLS_PER_HOUR="${MAX_CALLS_PER_HOUR:-100}"
Expand All @@ -59,6 +60,9 @@ CLAUDE_USE_CONTINUE="${CLAUDE_USE_CONTINUE:-true}"
CLAUDE_SESSION_FILE="$RALPH_DIR/.claude_session_id" # Session ID persistence file
CLAUDE_MIN_VERSION="2.0.76" # Minimum required Claude CLI version
CLAUDE_AUTO_UPDATE="${CLAUDE_AUTO_UPDATE:-true}" # Auto-update Claude CLI at startup
CLAUDE_CODE_CMD="${CLAUDE_CODE_CMD:-claude}" # Claude Code CLI command (default: global install)
CLAUDE_MODEL="${CLAUDE_MODEL:-}" # Model override (e.g. claude-sonnet-4-6); empty = CLI default
CLAUDE_EFFORT="${CLAUDE_EFFORT:-}" # Effort level override (e.g. high, low); empty = CLI default

# Session management configuration (Phase 1.2)
# Note: SESSION_EXPIRATION_SECONDS is defined in lib/response_analyzer.sh (86400 = 24 hours)
Expand Down Expand Up @@ -157,6 +161,8 @@ load_ralphrc() {
[[ -n "$_env_CB_AUTO_RESET" ]] && CB_AUTO_RESET="$_env_CB_AUTO_RESET"
[[ -n "$_env_CLAUDE_CODE_CMD" ]] && CLAUDE_CODE_CMD="$_env_CLAUDE_CODE_CMD"
[[ -n "$_env_CLAUDE_AUTO_UPDATE" ]] && CLAUDE_AUTO_UPDATE="$_env_CLAUDE_AUTO_UPDATE"
[[ -n "$_env_CLAUDE_MODEL" ]] && CLAUDE_MODEL="$_env_CLAUDE_MODEL"
[[ -n "$_env_CLAUDE_EFFORT" ]] && CLAUDE_EFFORT="$_env_CLAUDE_EFFORT"

RALPHRC_LOADED=true
return 0
Expand Down Expand Up @@ -1117,6 +1123,16 @@ build_claude_command() {
return 1
fi

# Add model override (Issue #228)
if [[ -n "${CLAUDE_MODEL:-}" ]]; then
CLAUDE_CMD_ARGS+=("--model" "$CLAUDE_MODEL")
fi

# Add effort level override (Issue #228)
if [[ -n "${CLAUDE_EFFORT:-}" ]]; then
CLAUDE_CMD_ARGS+=("--effort" "$CLAUDE_EFFORT")
fi

# Add output format flag
if [[ "$CLAUDE_OUTPUT_FORMAT" == "json" ]]; then
CLAUDE_CMD_ARGS+=("--output-format" "json")
Expand Down
119 changes: 118 additions & 1 deletion tests/unit/test_cli_modern.bats
Original file line number Diff line number Diff line change
Expand Up @@ -186,17 +186,21 @@ setup() {
return 0
}

# load_ralphrc - minimal version for testing CLAUDE_CODE_CMD loading
# load_ralphrc - minimal version for testing CLAUDE_CODE_CMD/MODEL/EFFORT loading
RALPHRC_FILE=".ralphrc"
RALPHRC_LOADED=false
_env_CLAUDE_CODE_CMD="${CLAUDE_CODE_CMD:-}"
_env_CLAUDE_MODEL="${CLAUDE_MODEL:-}"
_env_CLAUDE_EFFORT="${CLAUDE_EFFORT:-}"

load_ralphrc() {
if [[ ! -f "$RALPHRC_FILE" ]]; then
return 0
fi
source "$RALPHRC_FILE"
[[ -n "$_env_CLAUDE_CODE_CMD" ]] && CLAUDE_CODE_CMD="$_env_CLAUDE_CODE_CMD"
[[ -n "$_env_CLAUDE_MODEL" ]] && CLAUDE_MODEL="$_env_CLAUDE_MODEL"
[[ -n "$_env_CLAUDE_EFFORT" ]] && CLAUDE_EFFORT="$_env_CLAUDE_EFFORT"
RALPHRC_LOADED=true
return 0
}
Expand Down Expand Up @@ -507,6 +511,16 @@ build_claude_command() {
return 1
fi

# Add model override (Issue #228)
if [[ -n "${CLAUDE_MODEL:-}" ]]; then
CLAUDE_CMD_ARGS+=("--model" "$CLAUDE_MODEL")
fi

# Add effort level override (Issue #228)
if [[ -n "${CLAUDE_EFFORT:-}" ]]; then
CLAUDE_CMD_ARGS+=("--effort" "$CLAUDE_EFFORT")
fi

# Add output format flag
if [[ "$CLAUDE_OUTPUT_FORMAT" == "json" ]]; then
CLAUDE_CMD_ARGS+=("--output-format" "json")
Expand Down Expand Up @@ -1799,3 +1813,106 @@ EOF
run grep -c '^set -e' "$script"
[[ "$output" == "0" ]]
}

# =============================================================================
# Issue #228: CLAUDE_CODE_CMD env-snapshot fix
# =============================================================================

@test "CLAUDE_CODE_CMD default uses parameter expansion not unconditional assignment" {
# Regression test for #228: line must use := syntax so the env snapshot
# captures "" (unset) rather than "claude" when no env var was exported.
local script="${BATS_TEST_DIRNAME}/../../ralph_loop.sh"
run grep 'CLAUDE_CODE_CMD=' "$script"
# Should use parameter expansion (:-) not bare assignment before the snapshot block
[[ "$output" == *'CLAUDE_CODE_CMD="${CLAUDE_CODE_CMD:-claude}"'* ]]
}

@test "CLAUDE_CODE_CMD from .ralphrc is respected when no env var is set" {
# Core bug: when no env CLAUDE_CODE_CMD is exported, .ralphrc value must win
cat > "$TEST_DIR/.ralphrc" << 'EOF'
CLAUDE_CODE_CMD="npx @anthropic-ai/claude-code"
EOF
# Simulate correctly-initialized state (env not set → snapshot captured "")
_env_CLAUDE_CODE_CMD=""
CLAUDE_CODE_CMD="claude" # script default

load_ralphrc
assert_equal "$CLAUDE_CODE_CMD" "npx @anthropic-ai/claude-code"
}

@test "CLAUDE_MODEL from .ralphrc is loaded correctly" {
cat > "$TEST_DIR/.ralphrc" << 'EOF'
CLAUDE_MODEL="claude-sonnet-4-6"
EOF
_env_CLAUDE_MODEL=""
CLAUDE_MODEL=""

load_ralphrc
assert_equal "$CLAUDE_MODEL" "claude-sonnet-4-6"
}

@test "CLAUDE_EFFORT from .ralphrc is loaded correctly" {
cat > "$TEST_DIR/.ralphrc" << 'EOF'
CLAUDE_EFFORT="high"
EOF
_env_CLAUDE_EFFORT=""
CLAUDE_EFFORT=""

load_ralphrc
assert_equal "$CLAUDE_EFFORT" "high"
}

@test "CLAUDE_MODEL env var takes precedence over .ralphrc" {
cat > "$TEST_DIR/.ralphrc" << 'EOF'
CLAUDE_MODEL="claude-sonnet-4-6"
EOF
_env_CLAUDE_MODEL="claude-opus-4-6"
CLAUDE_MODEL="claude-opus-4-6"

load_ralphrc
assert_equal "$CLAUDE_MODEL" "claude-opus-4-6"
}

@test "CLAUDE_EFFORT env var takes precedence over .ralphrc" {
cat > "$TEST_DIR/.ralphrc" << 'EOF'
CLAUDE_EFFORT="low"
EOF
_env_CLAUDE_EFFORT="high"
CLAUDE_EFFORT="high"

load_ralphrc
assert_equal "$CLAUDE_EFFORT" "high"
}

@test "build_claude_command includes --model flag when CLAUDE_MODEL is set" {
CLAUDE_MODEL="claude-sonnet-4-6"
CLAUDE_EFFORT=""
echo "test prompt" > "$TEST_DIR/PROMPT.md"

build_claude_command "$TEST_DIR/PROMPT.md" "" ""

[[ "${CLAUDE_CMD_ARGS[*]}" == *"--model"* ]]
[[ "${CLAUDE_CMD_ARGS[*]}" == *"claude-sonnet-4-6"* ]]
}

@test "build_claude_command includes --effort flag when CLAUDE_EFFORT is set" {
CLAUDE_MODEL=""
CLAUDE_EFFORT="high"
echo "test prompt" > "$TEST_DIR/PROMPT.md"

build_claude_command "$TEST_DIR/PROMPT.md" "" ""

[[ "${CLAUDE_CMD_ARGS[*]}" == *"--effort"* ]]
[[ "${CLAUDE_CMD_ARGS[*]}" == *"high"* ]]
}

@test "build_claude_command omits --model and --effort when not configured" {
CLAUDE_MODEL=""
CLAUDE_EFFORT=""
echo "test prompt" > "$TEST_DIR/PROMPT.md"

build_claude_command "$TEST_DIR/PROMPT.md" "" ""

[[ "${CLAUDE_CMD_ARGS[*]}" != *"--model"* ]]
[[ "${CLAUDE_CMD_ARGS[*]}" != *"--effort"* ]]
}
Loading