From 95380b3cd58f7fbf575ebda0fe291224ba028317 Mon Sep 17 00:00:00 2001 From: Daisy Hollman Date: Fri, 9 Jan 2026 22:48:26 +0000 Subject: [PATCH] Add caffeinate plugin to prevent blocking sleep commands This plugin intercepts sleep commands in Bash and prompts Claude to use background execution (run_in_background: true) instead. This keeps the session responsive during long-running operations. Features: - Blocks sleep commands with helpful retry message - Allows sleep in quoted strings (false positive prevention) - Catches sleep in compound commands (&&, ||, ;) and loops - Allows background commands through (run_in_background: true) :house: Remote-Dev: homespace Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%) Claude-Steers: 6 Claude-Permission-Prompts: 0 Claude-Escapes: 0 --- .claude-plugin/marketplace.json | 11 ++++ plugins/caffeinate/.claude-plugin/plugin.json | 10 +++ plugins/caffeinate/README.md | 58 ++++++++++++++++++ plugins/caffeinate/hooks/hooks.json | 17 ++++++ .../caffeinate/hooks/scripts/check-sleep.sh | 61 +++++++++++++++++++ 5 files changed, 157 insertions(+) create mode 100644 plugins/caffeinate/.claude-plugin/plugin.json create mode 100644 plugins/caffeinate/README.md create mode 100644 plugins/caffeinate/hooks/hooks.json create mode 100755 plugins/caffeinate/hooks/scripts/check-sleep.sh diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 55547f5..e9578fe 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -328,6 +328,17 @@ "category": "productivity", "homepage": "https://github.com/anthropics/claude-plugins-official/tree/main/plugins/code-simplifier" }, + { + "name": "caffeinate", + "description": "Prevents blocking sleep commands by intercepting them and prompting Claude to use background execution instead. Keeps your session responsive during long-running operations.", + "author": { + "name": "Anthropic", + "email": "support@anthropic.com" + }, + "source": "./plugins/caffeinate", + "category": "productivity", + "homepage": "https://github.com/anthropics/claude-plugins-official/tree/main/plugins/caffeinate" + }, { "name": "explanatory-output-style", "description": "Adds educational insights about implementation choices and codebase patterns (mimics the deprecated Explanatory output style)", diff --git a/plugins/caffeinate/.claude-plugin/plugin.json b/plugins/caffeinate/.claude-plugin/plugin.json new file mode 100644 index 0000000..aaf7e7c --- /dev/null +++ b/plugins/caffeinate/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "caffeinate", + "version": "0.1.0", + "description": "Blocks sleep commands and reminds Claude to use background tasks instead", + "author": { + "name": "Anthropic", + "email": "support@anthropic.com" + }, + "keywords": ["productivity", "hooks", "bash", "background-tasks"] +} diff --git a/plugins/caffeinate/README.md b/plugins/caffeinate/README.md new file mode 100644 index 0000000..cd68f8b --- /dev/null +++ b/plugins/caffeinate/README.md @@ -0,0 +1,58 @@ +# Caffeinate + +A Claude Code plugin that blocks `sleep` commands and reminds Claude to use background tasks instead. + +## Purpose + +When Claude uses `sleep` commands in Bash, it blocks the session unnecessarily. This plugin intercepts these commands and suggests using proper async patterns like: + +- `run_in_background: true` parameter for long-running commands +- Polling for conditions instead of sleeping +- Proper async/background task patterns + +## Installation + +```bash +# From the plugin directory +claude --plugin-dir /path/to/caffeinate +``` + +Or copy to your project's `.claude-plugin/` directory. + +## What Gets Blocked + +| Command | Blocked? | Reason | +|---------|----------|--------| +| `sleep 5` | Yes | Direct sleep command | +| `sleep $TIMEOUT` | Yes | Sleep with variable | +| `echo "test" && sleep 5` | Yes | Sleep after separator, outside quotes | +| `cmd; sleep 10` | Yes | Sleep after semicolon | +| `echo "sleep 8 hours"` | No | Sleep is inside quotes (not a command) | +| `echo "foo && sleep 5"` | No | Entire sleep pattern is in a string | + +## How It Works + +The plugin uses a `PreToolUse` hook on the `Bash` tool to: + +1. Check if commands start with `sleep` +2. Detect `sleep` after command separators (`&&`, `||`, `;`, `|`) +3. Use quote-counting to avoid false positives when `sleep` appears inside strings + +## Alternatives to Sleep + +Instead of: +```bash +sleep 5 && check_status +``` + +Use background execution: +```bash +# Run in background with run_in_background: true +long_running_command +``` + +Or poll for conditions: +```bash +# Poll for a file to exist +while [ ! -f /tmp/ready ]; do :; done +``` diff --git a/plugins/caffeinate/hooks/hooks.json b/plugins/caffeinate/hooks/hooks.json new file mode 100644 index 0000000..e441827 --- /dev/null +++ b/plugins/caffeinate/hooks/hooks.json @@ -0,0 +1,17 @@ +{ + "description": "Blocks sleep commands and reminds Claude to use background tasks", + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/check-sleep.sh", + "timeout": 5 + } + ] + } + ] + } +} diff --git a/plugins/caffeinate/hooks/scripts/check-sleep.sh b/plugins/caffeinate/hooks/scripts/check-sleep.sh new file mode 100755 index 0000000..18113be --- /dev/null +++ b/plugins/caffeinate/hooks/scripts/check-sleep.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# Caffeinate hook: Blocks sleep commands and suggests background tasks +set -euo pipefail + +input=$(cat) +command=$(echo "$input" | jq -r '.tool_input.command // empty') + +if [[ -z "$command" ]]; then + exit 0 +fi + +# Allow sleep if run_in_background is true - that's the whole point! +run_in_bg=$(echo "$input" | jq -r '.tool_input.run_in_background // false') +if [[ "$run_in_bg" == "true" ]]; then + exit 0 +fi + +block_message='ACTION REQUIRED: Your sleep command was blocked. You MUST now call Bash again with the SAME command but add the parameter run_in_background: true. Do this immediately in your next tool call. Do not explain or apologize - just retry with run_in_background: true.' + +output_block() { + # Output JSON to stdout for blocking decision + # Output message to stderr to feed back to Claude + cat << EOF +{ + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "deny", + "permissionDecisionReason": "$block_message" + } +} +EOF + exit 0 +} + +# Remove all quoted strings to avoid false positives +# Replace single and double quoted strings with placeholder +cmd_no_quotes=$(echo "$command" | sed -E "s/\"[^\"]*\"//g; s/'[^']*'//g") + +# Check for sleep command patterns in the de-quoted command +# Pattern 1: Command starts with sleep +if [[ "$cmd_no_quotes" =~ (^|[[:space:]])sleep[[:space:]]+[0-9] ]]; then + output_block +fi + +# Pattern 2: sleep after any common separator (&&, ||, ;, |, do, then) +if [[ "$cmd_no_quotes" =~ (\&\&|;\||[[:space:]]do[[:space:]]|[[:space:]]then[[:space:]])[[:space:]]*sleep[[:space:]] ]]; then + output_block +fi + +# Pattern 3: Simple contains check - if "sleep " followed by number appears anywhere +if [[ "$cmd_no_quotes" =~ sleep[[:space:]]+[0-9] ]]; then + output_block +fi + +# Pattern 4: sleep with variable like $TIMEOUT or ${DELAY} +if [[ "$cmd_no_quotes" =~ sleep[[:space:]]+\$ ]]; then + output_block +fi + +# No sleep command detected - allow +exit 0