ISSUE: timeout: command not found on MacOS
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.
+1
brew install coreutils
then
alias timeout=gtimeout
seems to circumvent this.
brew install coreutilsthen
alias timeout=gtimeoutseems 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.
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 totimeoutif available - Store the detected command in a variable for reuse
- On Linux: use
-
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
timeoutcommand 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 &
- Change from:
-
Replace direct
timeoutcommand 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 &
- Change from:
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
unamecheck - If macOS, check for
gtimeoutcommand availability - If
gtimeoutnot found, add tomissing_depsarray with value "coreutils (for timeout command)"
- Detect if running on macOS using
-
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 line:
-
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
- Use
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
gtimeoutcommand 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_timeoutfunction (lines 210-217) to align with the new portable implementation:- Add comment explaining it mocks both
timeoutandgtimeout - Ensure it handles the same argument patterns as the real commands
- Add comment explaining it mocks both
-
Update
setup_mocks()function (around line 220-235):- Add mock for
gtimeoutcommand (same implementation as timeout mock) - Export the gtimeout function
- Add mock for
6. Installation Script Updates
Modify file:install.sh in the install_scripts() function (around line 84-151):
- Ensure the new
file:lib/timeout_utils.shis 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
🤖 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 generateto iterate on the previous version of the implementation plan.
Supported Commands (Inside Description)
- Add
@traycerai ignoreanywhere 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.