ralph-claude-code icon indicating copy to clipboard operation
ralph-claude-code copied to clipboard

ISSUE: timeout: command not found on MacOS

Open vjdhama opened this issue 3 months ago • 3 comments

Context

I am running ralph and I am errors in log file related to following line:

if timeout ${timeout_seconds}s "${CLAUDE_CMD_ARGS[@]}" > "$output_file" 2>&1 &

This leads to ralph just not moving forward at all.

Error

/Users/vjdhama/.ralph/ralph_loop.sh: line 586: timeout: command not found

Possible Root Cause

The timeout command is not present by default on macOS. Linux systems typically have it pre-installed from GNU coreutils.

Possible Solution

Wrap timeout in a function portable_timeout that provides cross-platform compatibility:

Alternative Fix

We can Install GNU coreutils via Homebrew in MacOS systems using following command:

brew install coreutils

We should add this information to the installation instructions in the README.md file for MacOS.

Happy to contribute on any agreed upon changes to make this work.

vjdhama avatar Jan 10 '26 09:01 vjdhama

+1

dvlkv avatar Jan 14 '26 15:01 dvlkv

brew install coreutils

then

alias timeout=gtimeout

seems to circumvent this.

frenchmustard avatar Jan 14 '26 20:01 frenchmustard

brew install coreutils

then

alias timeout=gtimeout

seems to circumvent this.

this is how I fixed this, it was an almost silent error, I had to take a look at logs file.

I think it should be a check like when trying to run ralph --monitor and an error pops if tmux is not installed. The user experience would be better to faster understand where the error is and how to fix it.

vchabot avatar Jan 15 '26 22:01 vchabot

Plan

Observations

The codebase uses the GNU timeout command in file:ralph_loop.sh (lines 870, 883) to limit Claude Code execution time. The command is not available on macOS by default but exists in GNU coreutils. The project already has cross-platform support patterns in file:lib/date_utils.sh using uname to detect Darwin vs Linux. The file:install.sh script checks dependencies like tmux, jq, and git but doesn't verify timeout availability. Test mocks already include a mock_timeout function.

Approach

Create a portable timeout wrapper function in a new utility library that automatically detects the platform and uses the appropriate timeout command (GNU timeout on Linux, gtimeout from coreutils on macOS). Add dependency checking during installation to detect missing coreutils on macOS and provide clear installation instructions. Update documentation to include macOS-specific setup requirements. This approach maintains backward compatibility while providing a seamless cross-platform experience.

Implementation Steps

1. Create Portable Timeout Utility

Create file:lib/timeout_utils.sh with cross-platform timeout functionality:

  • Add detect_timeout_command() function that checks for available timeout commands:

    • On Linux: use timeout (GNU coreutils, pre-installed)
    • On macOS: check for gtimeout (from Homebrew coreutils), fallback to timeout if available
    • Store the detected command in a variable for reuse
  • Add portable_timeout() function that wraps the timeout execution:

    • Accept timeout duration and command arguments
    • Use the detected timeout command from detect_timeout_command()
    • Preserve all command arguments and exit codes
    • Handle edge cases where no timeout command is available (return error with helpful message)
  • Export functions for use in other scripts

Pattern to follow: Use the same structure as file:lib/date_utils.sh which already implements cross-platform utilities using uname detection

2. Update Ralph Loop Script

Modify file:ralph_loop.sh:

  • Add source statement near the top (around line 10-15) to load the new timeout utilities:

    source "$RALPH_HOME/lib/timeout_utils.sh"
    
  • Replace direct timeout command usage at line 870:

    • Change from: if timeout ${timeout_seconds}s "${CLAUDE_CMD_ARGS[@]}" > "$output_file" 2>&1 &
    • Change to: if portable_timeout ${timeout_seconds}s "${CLAUDE_CMD_ARGS[@]}" > "$output_file" 2>&1 &
  • Replace direct timeout command usage at line 883:

    • Change from: if timeout ${timeout_seconds}s $CLAUDE_CODE_CMD < "$PROMPT_FILE" > "$output_file" 2>&1 &
    • Change to: if portable_timeout ${timeout_seconds}s $CLAUDE_CODE_CMD < "$PROMPT_FILE" > "$output_file" 2>&1 &

3. Add Dependency Checking

Modify file:install.sh in the check_dependencies() function (around line 34):

  • After the existing dependency checks (node, jq, git), add macOS-specific timeout check:

    • Detect if running on macOS using uname check
    • If macOS, check for gtimeout command availability
    • If gtimeout not found, add to missing_deps array with value "coreutils (for timeout command)"
  • Update the installation instructions section (around line 54-56) to include macOS coreutils:

    • Add line: echo " macOS: brew install node jq git coreutils"
    • Update existing macOS line to include coreutils
  • Add informational warning (not blocking) if coreutils is missing on macOS:

    • Use log "WARN" to notify user
    • Provide specific command: brew install coreutils
    • Explain that timeout functionality requires this package

4. Update Documentation

Modify file:README.md:

  • Update "System Requirements" section (around line 467-475):

    • Add bullet point for timeout command requirement
    • Specify that macOS users need GNU coreutils
  • Update "Installing tmux" section (around line 514-525) or create new "Installing Dependencies" section:

    • Add subsection for macOS coreutils installation
    • Include command: brew install coreutils
    • Explain that this provides the gtimeout command needed for execution timeouts
  • Update "Common Issues" section (around line 561-571):

    • Add entry for "timeout: command not found" error
    • Provide solution: install coreutils on macOS
    • Reference the installation instructions

5. Update Test Mocks

Modify file:tests/helpers/mocks.bash:

  • Update the mock_timeout function (lines 210-217) to align with the new portable implementation:

    • Add comment explaining it mocks both timeout and gtimeout
    • Ensure it handles the same argument patterns as the real commands
  • Update setup_mocks() function (around line 220-235):

    • Add mock for gtimeout command (same implementation as timeout mock)
    • Export the gtimeout function

6. Installation Script Updates

Modify file:install.sh in the install_scripts() function (around line 84-151):

  • Ensure the new file:lib/timeout_utils.sh is copied to $RALPH_HOME/lib/ directory
  • The existing line 91 cp -r "$SCRIPT_DIR/lib/"* "$RALPH_HOME/lib/" should handle this automatically
  • Verify the file is made executable (line 148 already handles this with wildcard)

Architecture Diagram

sequenceDiagram
    participant User
    participant install.sh
    participant ralph_loop.sh
    participant timeout_utils.sh
    participant System

    User->>install.sh: Run installation
    install.sh->>System: Check platform (uname)
    alt macOS detected
        install.sh->>System: Check for gtimeout
        alt gtimeout not found
            install.sh->>User: WARN: Install coreutils
            install.sh->>User: brew install coreutils
        end
    end
    install.sh->>System: Copy lib/timeout_utils.sh
    
    User->>ralph_loop.sh: Start Ralph
    ralph_loop.sh->>timeout_utils.sh: Source utilities
    timeout_utils.sh->>System: detect_timeout_command()
    alt Linux
        System-->>timeout_utils.sh: Use 'timeout'
    else macOS with coreutils
        System-->>timeout_utils.sh: Use 'gtimeout'
    else macOS without coreutils
        System-->>timeout_utils.sh: Error + instructions
    end
    
    ralph_loop.sh->>timeout_utils.sh: portable_timeout(duration, command)
    timeout_utils.sh->>System: Execute with detected timeout cmd
    System-->>ralph_loop.sh: Return result

Testing Considerations

  • Test the portable timeout function on both Linux and macOS environments
  • Verify installation script properly detects missing coreutils on macOS
  • Ensure existing test suite continues to pass with mock timeout functions
  • Test graceful error handling when timeout command is unavailable
  • Verify that timeout functionality works correctly with both modern and legacy CLI modes

Import In IDE

VSCode Icon Cursor Icon Windsurf Icon

🤖 Prompt for AI Agents
## Observations

The codebase uses the GNU `timeout` command in `file:ralph_loop.sh` (lines 870, 883) to limit Claude Code execution time. The command is not available on macOS by default but exists in GNU coreutils. The project already has cross-platform support patterns in `file:lib/date_utils.sh` using `uname` to detect Darwin vs Linux. The `file:install.sh` script checks dependencies like tmux, jq, and git but doesn't verify timeout availability. Test mocks already include a `mock_timeout` function.

## Approach

Create a portable timeout wrapper function in a new utility library that automatically detects the platform and uses the appropriate timeout command (GNU `timeout` on Linux, `gtimeout` from coreutils on macOS). Add dependency checking during installation to detect missing coreutils on macOS and provide clear installation instructions. Update documentation to include macOS-specific setup requirements. This approach maintains backward compatibility while providing a seamless cross-platform experience.

## Implementation Steps

### 1. Create Portable Timeout Utility

**Create** `file:lib/timeout_utils.sh` with cross-platform timeout functionality:

- Add `detect_timeout_command()` function that checks for available timeout commands:
  - On Linux: use `timeout` (GNU coreutils, pre-installed)
  - On macOS: check for `gtimeout` (from Homebrew coreutils), fallback to `timeout` if available
  - Store the detected command in a variable for reuse
  
- Add `portable_timeout()` function that wraps the timeout execution:
  - Accept timeout duration and command arguments
  - Use the detected timeout command from `detect_timeout_command()`
  - Preserve all command arguments and exit codes
  - Handle edge cases where no timeout command is available (return error with helpful message)

- Export functions for use in other scripts

**Pattern to follow**: Use the same structure as `file:lib/date_utils.sh` which already implements cross-platform utilities using `uname` detection

### 2. Update Ralph Loop Script

**Modify** `file:ralph_loop.sh`:

- Add source statement near the top (around line 10-15) to load the new timeout utilities:
  ```bash
  source "$RALPH_HOME/lib/timeout_utils.sh"
  ```

- Replace direct `timeout` command usage at line 870:
  - Change from: `if timeout ${timeout_seconds}s "${CLAUDE_CMD_ARGS[@]}" > "$output_file" 2>&1 &`
  - Change to: `if portable_timeout ${timeout_seconds}s "${CLAUDE_CMD_ARGS[@]}" > "$output_file" 2>&1 &`

- Replace direct `timeout` command usage at line 883:
  - Change from: `if timeout ${timeout_seconds}s $CLAUDE_CODE_CMD < "$PROMPT_FILE" > "$output_file" 2>&1 &`
  - Change to: `if portable_timeout ${timeout_seconds}s $CLAUDE_CODE_CMD < "$PROMPT_FILE" > "$output_file" 2>&1 &`

### 3. Add Dependency Checking

**Modify** `file:install.sh` in the `check_dependencies()` function (around line 34):

- After the existing dependency checks (node, jq, git), add macOS-specific timeout check:
  - Detect if running on macOS using `uname` check
  - If macOS, check for `gtimeout` command availability
  - If `gtimeout` not found, add to `missing_deps` array with value "coreutils (for timeout command)"
  
- Update the installation instructions section (around line 54-56) to include macOS coreutils:
  - Add line: `echo "  macOS: brew install node jq git coreutils"`
  - Update existing macOS line to include coreutils

- Add informational warning (not blocking) if coreutils is missing on macOS:
  - Use `log "WARN"` to notify user
  - Provide specific command: `brew install coreutils`
  - Explain that timeout functionality requires this package

### 4. Update Documentation

**Modify** `file:README.md`:

- Update "System Requirements" section (around line 467-475):
  - Add bullet point for timeout command requirement
  - Specify that macOS users need GNU coreutils
  
- Update "Installing tmux" section (around line 514-525) or create new "Installing Dependencies" section:
  - Add subsection for macOS coreutils installation
  - Include command: `brew install coreutils`
  - Explain that this provides the `gtimeout` command needed for execution timeouts

- Update "Common Issues" section (around line 561-571):
  - Add entry for "timeout: command not found" error
  - Provide solution: install coreutils on macOS
  - Reference the installation instructions

### 5. Update Test Mocks

**Modify** `file:tests/helpers/mocks.bash`:

- Update the `mock_timeout` function (lines 210-217) to align with the new portable implementation:
  - Add comment explaining it mocks both `timeout` and `gtimeout`
  - Ensure it handles the same argument patterns as the real commands

- Update `setup_mocks()` function (around line 220-235):
  - Add mock for `gtimeout` command (same implementation as timeout mock)
  - Export the gtimeout function

### 6. Installation Script Updates

**Modify** `file:install.sh` in the `install_scripts()` function (around line 84-151):

- Ensure the new `file:lib/timeout_utils.sh` is copied to `$RALPH_HOME/lib/` directory
- The existing line 91 `cp -r "$SCRIPT_DIR/lib/"* "$RALPH_HOME/lib/"` should handle this automatically
- Verify the file is made executable (line 148 already handles this with wildcard)

## Architecture Diagram

```mermaid
sequenceDiagram
    participant User
    participant install.sh
    participant ralph_loop.sh
    participant timeout_utils.sh
    participant System

    User->>install.sh: Run installation
    install.sh->>System: Check platform (uname)
    alt macOS detected
        install.sh->>System: Check for gtimeout
        alt gtimeout not found
            install.sh->>User: WARN: Install coreutils
            install.sh->>User: brew install coreutils
        end
    end
    install.sh->>System: Copy lib/timeout_utils.sh
    
    User->>ralph_loop.sh: Start Ralph
    ralph_loop.sh->>timeout_utils.sh: Source utilities
    timeout_utils.sh->>System: detect_timeout_command()
    alt Linux
        System-->>timeout_utils.sh: Use 'timeout'
    else macOS with coreutils
        System-->>timeout_utils.sh: Use 'gtimeout'
    else macOS without coreutils
        System-->>timeout_utils.sh: Error + instructions
    end
    
    ralph_loop.sh->>timeout_utils.sh: portable_timeout(duration, command)
    timeout_utils.sh->>System: Execute with detected timeout cmd
    System-->>ralph_loop.sh: Return result
```

## Testing Considerations

- Test the portable timeout function on both Linux and macOS environments
- Verify installation script properly detects missing coreutils on macOS
- Ensure existing test suite continues to pass with mock timeout functions
- Test graceful error handling when timeout command is unavailable
- Verify that timeout functionality works correctly with both modern and legacy CLI modes

Execution Information

Branch: main Commit: 509a9699a8c003dda8377be9f75e0af7f18ce94e


:bulb: Tips

Supported Commands (Inside Comments)

  • Use @traycerai generate to iterate on the previous version of the implementation plan.

Supported Commands (Inside Description)

  • Add @traycerai ignore anywhere in the ticket description to prevent this ticket from being processed.
  • Add @traycerai branch:<branch-name> anywhere in the ticket description to specify the target branch for the implementation plan.

Community

  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

traycerai[bot] avatar Jan 20 '26 12:01 traycerai[bot]