Anthropic and OpenAI enforce a 5-hour usage window - once I make my first request, I have 5 hours fixed usage before the window expires. This meant:
- ❌ Unpredictable availability - window could expire mid-project or at random times during the work day
- ❌ Frustration when usage windows expire at unopportune times
- ❌ Consistent work schedule and usage window planning
I created a cron-based wrapper script that automatically activates both Claude Code and Codex on a predictable schedule (7 AM, 12 PM, 5 PM, 10 PM). Now:
- ✅ 5-hour windows start at predictable times throughout the day
- ✅ I can plan around "new usage window" consistently at 7am, 12pm, 5pm, 10pm throughout the day
- ✅ Logging for confirming execution/start of windows
- ✅ Zero manual intervention needed
This guide shares my setup so you can implement a similar approach that fits your work style.
Create /home/code/bin/ai-cli-cron:
#!/bin/bash
# AI CLI tools wrapper for scheduled activation of usage windows
# Outputs JSON for machine-readable logs with human-friendly summaries
# ============================================================================
# CONFIGURATION - Update these values for your environment
# ============================================================================
# System paths
USER_HOME="/home/code"
NVM_DIR="$USER_HOME/.nvm"
NODE_VERSION="v22.18.0"
# Tool locations
CLAUDE_BIN="$USER_HOME/.local/bin/claude"
CODEX_BIN="$USER_HOME/.local/bin/codex"
# Timezone (see: timedatectl list-timezones)
TIMEZONE="America/Denver"
# MCP server API keys (replace with your actual keys, optional and only if seeing MCP errors)
FIRECRAWL_API_KEY="your-firecrawl-api-key-here"
OPENMEMORY_API_KEY="your-openmemory-api-key-here"
# Health check prompt
HEALTH_CHECK_PROMPT="Respond with 'Ready' if operational"
# ============================================================================
# ENVIRONMENT SETUP
# ============================================================================
export HOME="$USER_HOME"
export NVM_DIR="$NVM_DIR"
export PATH="$NVM_DIR/versions/node/$NODE_VERSION/bin:$USER_HOME/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
export TZ="$TIMEZONE"
export FIRECRAWL_API_KEY
export OPENMEMORY_API_KEY
export CLIENT_NAME="claude-code"
# ============================================================================
# LOGGING HELPER
# ============================================================================
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
# Parse Claude JSON response and extract key info
parse_claude_json() {
local json="$1"
local result=$(echo "$json" | jq -r '.result // "ERROR"')
local duration=$(echo "$json" | jq -r '.duration_ms // 0')
local cost=$(echo "$json" | jq -r '.total_cost_usd // 0')
echo "result=$result, duration=${duration}ms, cost=\$${cost}"
}
# Parse Codex JSONL response and extract key info
parse_codex_jsonl() {
local jsonl="$1"
local last_line=$(echo "$jsonl" | tail -1)
local type=$(echo "$last_line" | jq -r '.type // "unknown"')
if [[ "$type" == "turn.completed" ]]; then
local tokens=$(echo "$last_line" | jq -r '.usage.input_tokens // 0')
echo "status=completed, tokens=${tokens}"
elif [[ "$type" == "turn.failed" ]]; then
echo "status=failed"
else
echo "status=unknown"
fi
}
# ============================================================================
# TOOL EXECUTION
# ============================================================================
# Run Claude
log "Claude job started"
CLAUDE_OUTPUT=$("$CLAUDE_BIN" --output-format json -p "$HEALTH_CHECK_PROMPT" 2>&1)
CLAUDE_EXIT=$?
echo "$CLAUDE_OUTPUT"
log "Claude job completed (exit: $CLAUDE_EXIT) - $(parse_claude_json "$CLAUDE_OUTPUT")"
echo ""
# Run Codex
log "Codex job started"
CODEX_OUTPUT=$("$CODEX_BIN" exec --skip-git-repo-check --json "$HEALTH_CHECK_PROMPT" 2>&1)
CODEX_EXIT=$?
echo "$CODEX_OUTPUT"
log "Codex job completed (exit: $CODEX_EXIT) - $(parse_codex_jsonl "$CODEX_OUTPUT")"
echo ""Make it executable:
chmod +x /home/code/bin/ai-cli-cron-
Organized Configuration: All customizable settings are grouped at the top:
- System paths and tool locations
- Timezone settings
- API keys
- Health check prompt
-
Clean Environment Setup: Cron runs with minimal environment variables. The script explicitly sets:
PATHto include Node.js, local binaries, and system pathsHOME,NVM_DIRfor proper tool resolutionTZfor consistent timezone handling across all timestamp operations- MCP server API keys (optional)
-
JSON Output with Summaries:
- Both CLI tools output machine-readable JSON
- Claude uses
--output-format jsonfor structured response data - Codex uses
--jsonfor JSONL (JSON Lines) event streaming - Helper functions parse JSON and display human-friendly summaries (status, duration, cost, tokens)
-
Timestamp Logging: Every operation is logged with consistent timestamps in your configured timezone
-
Health Checks: Simple "Ready" prompts verify both tools are operational
-
Exit Code Tracking: Captures and logs exit codes to verify successful execution
-
Dependency: Requires
jqfor JSON parsing (install:sudo apt install jq)
I run the script 4 times per day at 5-hour intervals to maximize coverage throughout my working hours:
0 7,12,17,22 * * * /home/code/bin/ai-cli-cron >> /home/code/logs/ai-cli-cron.log 2>&1Schedule breakdown:
- 7:00 AM - Morning window (covers 7 AM - 12 PM work session)
- 12:00 PM - Midday window (covers 12 PM - 5 PM work session)
- 5:00 PM - Evening window (covers 5 PM - 10 PM work session)
- 10:00 PM - Late night window (covers 10 PM - 3 AM if needed)
Why this works:
- Each activation starts a new 5-hour usage window
- 5-hour spacing ensures minimal overlap while maximizing availability
- Covers typical work hours (7 AM - 10 PM) plus late night if needed
- Predictable schedule - morning window > after-lunch window > evening window
Edit your crontab:
crontab -eAdd the line above, adjusting times to match your work schedule. The format is:
minute hour * * * command
Tips for customizing:
- Adjust times to start 30-60 minutes before you typically work
- Use comma-separated hours (e.g.,
7,12,17,22) for specific times - Ensure at least 5 hours between runs to establish new 5h session
mkdir -p /home/code/logsTo prevent logs from growing indefinitely, create /etc/logrotate.d/ai-cli-cron:
/home/code/logs/ai-cli-cron.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
}
The script now outputs JSON for each tool, followed by a human-readable summary line. Example:
[2025-10-07 10:26:06] Claude job started
{"type":"result","subtype":"success","is_error":false,"duration_ms":2123,"result":"Ready","total_cost_usd":0.007160099999999999,...}
[2025-10-07 10:26:19] Claude job completed (exit: 0) - result=Ready, duration=2123ms, cost=$0.00716
[2025-10-07 10:26:19] Codex job started
{"type":"thread.started","thread_id":"0199bf7e-c1b1-7861-85b2-110c05bb9706"}
{"type":"turn.started"}
{"type":"item.completed","item":{"id":"item_0","type":"reasoning","text":"**Preparing initial response**"}}
{"type":"item.completed","item":{"id":"item_1","type":"agent_message","text":"Ready"}}
{"type":"turn.completed","usage":{"input_tokens":2937,"cached_input_tokens":2048,"output_tokens":7}}
[2025-10-07 10:26:22] Codex job completed (exit: 0) - status=completed, tokens=2937
Benefits:
- JSON lines can be parsed by log analysis tools
- Summary lines provide quick status at a glance
- Full event data preserved for debugging
With my schedule running 4 times daily (7 AM, 12 PM, 5 PM, 10 PM), you should see 8 job completions per day (4 Claude + 4 Codex).
View the most recent runs:
tail -50 /home/code/logs/ai-cli-cron.log | grep "job completed"Expected output (shows both tools succeeded):
[2025-10-07 07:00:16] Claude job completed (exit: 0) - result=Ready, duration=2123ms, cost=$0.00716
[2025-10-07 07:00:19] Codex job completed (exit: 0) - status=completed, tokens=2937
Monitor in real-time (useful during troubleshooting):
tail -f /home/code/logs/ai-cli-cron.logPress Ctrl+C to exit.
See all runs from today:
grep "$(date '+%Y-%m-%d')" /home/code/logs/ai-cli-cron.log | grep "job completed"Check that your cron job is active:
# List your cron jobs
crontab -l
# Check cron service status
systemctl status cron
# View recent cron executions in system logs
grep "ai-cli-cron" /var/log/syslog | tail -20If jobs aren't running, check:
- Cron service is active:
sudo systemctl start cron - Script is executable:
chmod +x /home/code/bin/ai-cli-cron - Log directory exists:
mkdir -p /home/code/logs
- Check cron service:
systemctl status cron - Check crontab syntax:
crontab -l - Verify script permissions:
ls -l /home/code/bin/ai-cli-cron - Check cron logs:
grep ai-cli-cron /var/log/syslog
If tools can't be found:
- Verify paths in script match your installation:
which claude which codex which node
- Update
PATHvariable in script accordingly
Ensure your system timezone matches the script:
timedatectl # Check system timezone
export TZ=America/Denver # Test in shell firstSome AI providers (like the one I use for Codex) enforce 5-hour usage windows where you can only make requests within a 5-hour period after your first request. This creates a problem: if you start working at unpredictable times, your usage window might expire during important work.
My solution: Run the script on a predictable schedule to pre-activate these windows at optimal times.
By running at 7 AM, 12 PM, 5 PM, and 10 PM, I ensure:
- Coverage during work hours: No matter when I start working, a window is active or will activate soon
- Predictable availability: I know exactly when each 5-hour window starts and ends
- No wasted activations: 5-hour spacing means each run starts a fresh window without overlap
- Late-night flexibility: The 10 PM run covers unexpected late-night work sessions
Visual timeline:
7 AM activation → covers 7 AM - 12 PM
12 PM activation → covers 12 PM - 5 PM
5 PM activation → covers 5 PM - 10 PM
10 PM activation → covers 10 PM - 3 AM
This approach transforms an unpredictable constraint into a reliable, automated system.
Edit the configuration section at the top of /home/code/bin/ai-cli-cron:
-
Verify paths:
which claude # Should match CLAUDE_BIN which codex # Should match CODEX_BIN which node # Check Node version matches NODE_VERSION
-
Update these variables:
USER_HOME- Your home directory (default:/home/code)NODE_VERSION- Your Node.js version (find with:node --version)TIMEZONE- Your timezone (list:timedatectl list-timezones)FIRECRAWL_API_KEY,OPENMEMORY_API_KEY- Your actual API keys
-
Adjust the schedule in your crontab to match your work hours:
crontab -e # Change 7,12,17,22 to your preferred hours
To add additional AI CLI tools, insert in the "TOOL EXECUTION" section:
# Run YourTool
log "YourTool job started"
TOOL_OUTPUT=$("$YOUR_TOOL_BIN" --json "health check prompt" 2>&1)
TOOL_EXIT=$?
echo "$TOOL_OUTPUT"
log "YourTool job completed (exit: $TOOL_EXIT)"
echo ""Add to your crontab (requires mail configured):
MAILTO=[email protected]
0 7,12,17,22 * * * /home/code/bin/ai-cli-cron >> /home/code/logs/ai-cli-cron.log 2>&1✅ Predictable usage windows starting at optimal times
✅ Automated health checks verify tools are operational
✅ Machine-readable output - JSON/JSONL format for log analysis
✅ Human-friendly summaries - quick status overview without parsing JSON
✅ Comprehensive logging with consistent timezone-aware timestamps
✅ Cost and token tracking - automatically extracted and logged
✅ No manual intervention needed
✅ Easy to customize - all configuration at the top of the script
✅ Clean and maintainable - streamlined, professional code
✅ Multiple tools managed in one script
✅ Shareable - clearly documented configuration section
Note: Adjust paths, timezones, and schedules to match your specific setup and workflow needs.