Skip to content
Open
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
14 changes: 14 additions & 0 deletions .openhands/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"stop": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": ".openhands/hooks/on_stop.sh",
"timeout": 300
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Acceptable - Timeout: 300 seconds (5 minutes) for pre-commit is generous but reasonable for large repos or slow CI environments.

If this becomes a bottleneck in practice, consider making it configurable or running --files on changed files only (though --all-files is the safer default for agent work).

}
]
}
]
}
68 changes: 68 additions & 0 deletions .openhands/hooks/on_stop.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/bin/bash
# Stop hook: runs pre-commit before allowing agent to finish
#
# This hook runs when the agent attempts to stop/finish.
# It can BLOCK the stop by:
# - Exiting with code 2 (blocked)
# - Outputting JSON: {"decision": "deny", "additionalContext": "feedback message"}
#
# Environment variables available:
# OPENHANDS_PROJECT_DIR - Project directory
# OPENHANDS_SESSION_ID - Session ID
# GITHUB_TOKEN - GitHub API token (if available)

set -o pipefail

PROJECT_DIR="${OPENHANDS_PROJECT_DIR:-$(pwd)}"
cd "$PROJECT_DIR" || exit 1

# Collect all issues to report back to the agent
ISSUES=""
BLOCK_STOP=false

log_issue() {
ISSUES="${ISSUES}${1}\n"
BLOCK_STOP=true
}

>&2 echo "=== Stop Hook ==="
>&2 echo "Project directory: $PROJECT_DIR"
>&2 echo ""

# --------------------------
# Step 1: Run pre-commit on all files
# --------------------------
>&2 echo "=== Running pre-commit run --all-files ==="
if command -v uv &> /dev/null; then
PRECOMMIT_OUTPUT=$(uv run pre-commit run --all-files 2>&1)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion - Error Handling: What happens if pre-commit isn't installed at all (neither via uv nor standalone)?

The command will fail and PRECOMMIT_EXIT will be non-zero, causing the hook to block with a message about "pre-commit checks failed" - but the real issue is that pre-commit isn't available. The agent might waste time trying to "fix" non-existent issues.

Consider adding an explicit check:

if ! command -v uv &> /dev/null && ! command -v pre-commit &> /dev/null; then
    >&2 echo "⚠️  Neither 'uv' nor 'pre-commit' found in PATH"
    log_issue "## Pre-commit Not Found\n\nCannot run pre-commit checks. Please install pre-commit or uv."
fi

This makes the failure mode explicit rather than cryptic.

PRECOMMIT_EXIT=$?
else
PRECOMMIT_OUTPUT=$(pre-commit run --all-files 2>&1)
PRECOMMIT_EXIT=$?
fi

>&2 echo "$PRECOMMIT_OUTPUT"

if [ $PRECOMMIT_EXIT -ne 0 ]; then
>&2 echo "⚠️ pre-commit found issues (exit code: $PRECOMMIT_EXIT)"
log_issue "## Pre-commit Failed\n\nPre-commit checks failed. Please fix the following issues:\n\n\`\`\`\n${PRECOMMIT_OUTPUT}\n\`\`\`"
else
>&2 echo "✓ pre-commit passed"
fi
>&2 echo ""

# --------------------------
# Final decision
# --------------------------
if [ "$BLOCK_STOP" = true ]; then
>&2 echo "=== BLOCKING STOP: Issues found ==="
# Output JSON to provide feedback to the agent
# Escape the issues for JSON
ESCAPED_ISSUES=$(echo -e "$ISSUES" | jq -Rs .)
echo "{\"decision\": \"deny\", \"reason\": \"Pre-commit checks failed\", \"additionalContext\": $ESCAPED_ISSUES}"
exit 2
fi

>&2 echo "=== All checks passed, allowing stop ==="
echo '{"decision": "allow"}'
exit 0
Loading