|
#!/bin/bash |
|
# |
|
# Local Claude Code Handler for GitHub Issues |
|
# Processes GitHub issues by creating branches and calling Claude Code directly |
|
# |
|
|
|
set -e |
|
|
|
# Get environment variables passed from the monitor |
|
ISSUE_NUMBER="${ISSUE_NUMBER}" |
|
ISSUE_TITLE="${ISSUE_TITLE}" |
|
ISSUE_AUTHOR="${ISSUE_AUTHOR}" |
|
ISSUE_COMMAND="${ISSUE_COMMAND}" |
|
REPO_OWNER="${REPO_OWNER}" |
|
REPO_NAME="${REPO_NAME}" |
|
GITHUB_TOKEN="${GITHUB_TOKEN}" |
|
|
|
# Configuration |
|
REPO="${REPO_OWNER}/${REPO_NAME}" |
|
BRANCH_NAME="claude-bot/issue-${ISSUE_NUMBER}" |
|
|
|
# Get project root |
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" |
|
PROJECT_ROOT="$( cd "$SCRIPT_DIR/../.." && pwd )" |
|
|
|
# Log file location |
|
LOG_DIR="$PROJECT_ROOT/logs/claude-handler" |
|
mkdir -p "$LOG_DIR" |
|
LOG_FILE="$LOG_DIR/handler-local-$(date +%Y%m%d).log" |
|
|
|
# Function to log with timestamp |
|
log() { |
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" |
|
} |
|
|
|
# Function to add a comment to the issue |
|
add_issue_comment() { |
|
local comment="$1" |
|
log "Adding comment to issue #$ISSUE_NUMBER" |
|
|
|
gh issue comment "$ISSUE_NUMBER" \ |
|
--repo "$REPO" \ |
|
--body "$comment" |
|
} |
|
|
|
# Function to get the default branch of the repository |
|
get_default_branch() { |
|
gh repo view "$REPO" --json defaultBranchRef --jq '.defaultBranchRef.name' |
|
} |
|
|
|
# Set up Node.js environment for Claude Code |
|
setup_node_env() { |
|
log "Setting up Node.js environment..." |
|
|
|
# Load NVM if available |
|
if [ -s "$HOME/.nvm/nvm.sh" ]; then |
|
source "$HOME/.nvm/nvm.sh" |
|
nvm use 22.16.0 >/dev/null 2>&1 || { |
|
log "Installing Node.js 22.16.0..." |
|
nvm install 22.16.0 |
|
nvm use 22.16.0 |
|
} |
|
elif [ -s "/usr/local/share/nvm/nvm.sh" ]; then |
|
source "/usr/local/share/nvm/nvm.sh" |
|
nvm use 22.16.0 >/dev/null 2>&1 || { |
|
log "Installing Node.js 22.16.0..." |
|
nvm install 22.16.0 |
|
nvm use 22.16.0 |
|
} |
|
fi |
|
|
|
NODE_VERSION=$(node --version) |
|
log "Using Node.js: $NODE_VERSION" |
|
} |
|
|
|
log "=== Processing Issue #$ISSUE_NUMBER Locally ===" |
|
log "Title: $ISSUE_TITLE" |
|
log "Author: $ISSUE_AUTHOR" |
|
log "Command: $ISSUE_COMMAND" |
|
log "Branch will be: $BRANCH_NAME" |
|
|
|
# Get the default branch |
|
DEFAULT_BRANCH=$(get_default_branch) |
|
log "Repository default branch: $DEFAULT_BRANCH" |
|
|
|
# First, acknowledge the issue |
|
ACKNOWLEDGMENT="👋 Hello @$ISSUE_AUTHOR! I'm Claude, and I'll help you with this issue. |
|
|
|
I'm analyzing your request and will create a branch to work on it locally. Here's what I understand: |
|
|
|
**Request**: $ISSUE_COMMAND |
|
|
|
I'll start working on this and provide updates as I progress. Creating branch \`$BRANCH_NAME\` from \`$DEFAULT_BRANCH\`..." |
|
|
|
add_issue_comment "$ACKNOWLEDGMENT" |
|
|
|
# Change to project root |
|
cd "$PROJECT_ROOT" |
|
|
|
# Clean up any stale git state |
|
log "Cleaning up git state" |
|
git reset --hard HEAD 2>/dev/null || true |
|
git clean -fd 2>/dev/null || true |
|
git checkout "$DEFAULT_BRANCH" 2>/dev/null || true |
|
|
|
# Configure Git for GitHub operations |
|
log "Configuring Git authentication" |
|
git config --global user.name "Claude Bot" |
|
git config --global user.email "[email protected]" |
|
|
|
# Clear any existing credential helpers |
|
git config --global --unset-all credential.helper || true |
|
|
|
# Set up token-based authentication |
|
if [ -n "$GITHUB_TOKEN" ]; then |
|
log "Using GITHUB_TOKEN for authentication" |
|
# Create credential helper that returns the token |
|
git config --global credential.helper "!f() { |
|
if [ \"\$1\" = \"get\" ]; then |
|
echo \"username=token\" |
|
echo \"password=$GITHUB_TOKEN\" |
|
fi |
|
}; f" |
|
|
|
# Also set the remote URL to use token authentication |
|
CURRENT_REMOTE=$(git config --get remote.origin.url || echo "") |
|
if [[ "$CURRENT_REMOTE" == *"github.com"* ]] && [[ "$CURRENT_REMOTE" != *"@"* ]]; then |
|
# Convert https://github.com/... to https://token:[email protected]/... |
|
NEW_REMOTE=$(echo "$CURRENT_REMOTE" | sed "s|https://github.com/|https://token:[email protected]/|") |
|
git remote set-url origin "$NEW_REMOTE" |
|
log "Updated remote URL for token auth" |
|
fi |
|
else |
|
log "Using gh CLI for authentication" |
|
git config --global credential.helper "!gh auth git-credential" |
|
fi |
|
|
|
# Create and switch to the branch |
|
log "Creating branch: $BRANCH_NAME" |
|
|
|
# Clean up ALL existing claude-bot branch references (not just current) |
|
log "Cleaning up all claude-bot branch references" |
|
git branch -D $(git branch | grep "claude-bot" | xargs) 2>/dev/null || true |
|
git branch -d -r $(git branch -r | grep "origin/claude-bot" | xargs) 2>/dev/null || true |
|
rm -rf ".git/refs/remotes/origin/claude-bot" 2>/dev/null || true |
|
rm -f ".git/refs/remotes/origin/claude-bot"* 2>/dev/null || true |
|
find .git -name "*.lock" -delete 2>/dev/null || true |
|
|
|
# Ensure git directories have proper permissions |
|
mkdir -p .git/logs/refs/heads .git/refs/remotes/origin |
|
chmod -R 755 .git/logs .git/refs 2>/dev/null || true |
|
chown -R $(whoami):$(whoami) .git/logs .git/refs 2>/dev/null || true |
|
|
|
# First switch away from default branch to avoid fetch conflicts |
|
git checkout HEAD~0 2>/dev/null || true |
|
|
|
# Fetch updates from origin |
|
log "Fetching updates from origin" |
|
git fetch origin || { |
|
log "Error: Could not fetch from origin. Check GitHub authentication." |
|
add_issue_comment "❌ Error: Could not fetch from GitHub repository. Please check authentication setup." |
|
exit 1 |
|
} |
|
|
|
# Switch to default branch first, then create new branch |
|
git checkout "$DEFAULT_BRANCH" || { |
|
log "Error: Could not switch to default branch $DEFAULT_BRANCH" |
|
add_issue_comment "❌ Error: Could not switch to default branch \`$DEFAULT_BRANCH\`." |
|
exit 1 |
|
} |
|
|
|
git checkout -b "$BRANCH_NAME" || { |
|
log "Error: Could not create branch $BRANCH_NAME" |
|
add_issue_comment "❌ Error: Could not create branch \`$BRANCH_NAME\`. Please check for permission issues." |
|
exit 1 |
|
} |
|
|
|
# Update issue with execution status |
|
EXECUTION_COMMENT="🚀 **Starting work on branch \`$BRANCH_NAME\`** |
|
|
|
I'm now working on your request locally with Claude Code. I'll implement the changes and run tests before creating a pull request. |
|
|
|
**Current status**: Analyzing request and implementing changes..." |
|
|
|
add_issue_comment "$EXECUTION_COMMENT" |
|
|
|
# Set up Node.js environment |
|
setup_node_env |
|
|
|
# Create a direct command prompt for Claude Code |
|
CLAUDE_PROMPT_FILE="/tmp/claude_issue_${ISSUE_NUMBER}_prompt.txt" |
|
cat > "$CLAUDE_PROMPT_FILE" << EOF |
|
You are working on GitHub issue #$ISSUE_NUMBER: "$ISSUE_TITLE" within this repository. |
|
|
|
REQUEST: $ISSUE_COMMAND |
|
|
|
I need you to implement this request now. Here's what you should do: |
|
|
|
1. Read the project structure and understand the codebase |
|
2. Implement the specific request above |
|
3. Follow existing patterns and conventions |
|
4. Create or modify files as needed |
|
5. Do not create placeholder files - implement actual functionality |
|
|
|
The current branch is: $BRANCH_NAME |
|
|
|
Start by exploring the codebase and then implement the request completely. |
|
EOF |
|
|
|
log "Launching Claude Code with issue prompt..." |
|
|
|
# Set up environment variables for Claude Code automation |
|
export CLAUDE_PROMPT_FILE="$CLAUDE_PROMPT_FILE" |
|
export CLAUDE_AUTOMODE="true" |
|
export CI="true" |
|
export ANTHROPIC_AUTO_APPROVE="true" |
|
export CLAUDE_NON_INTERACTIVE="true" |
|
|
|
# Run Claude Code with the prompt file |
|
log "Executing Claude Code in automated mode..." |
|
CLAUDE_EXIT_CODE=0 |
|
|
|
# Try the simplest approach first - just use --print for text generation |
|
log "Using Claude Code --print for text generation..." |
|
timeout 960 claude --print --dangerously-skip-permissions < "$CLAUDE_PROMPT_FILE" > "/tmp/claude_output_${ISSUE_NUMBER}.txt" 2>&1 || CLAUDE_EXIT_CODE=$? |
|
|
|
# If that fails, try a simple interactive approach with timeout |
|
if [ $CLAUDE_EXIT_CODE -ne 0 ]; then |
|
log "Text generation failed, trying interactive approach with extended timeout..." |
|
CLAUDE_EXIT_CODE=0 |
|
|
|
# Simple approach: send prompt and exit command with extended timeout |
|
timeout 1920 bash -c " |
|
{ |
|
cat '$CLAUDE_PROMPT_FILE' |
|
echo '' |
|
echo 'Please complete this task and implement the changes needed.' |
|
echo '' |
|
# Give extended time for processing then send exit |
|
sleep 160 |
|
echo 'exit' |
|
} | claude --dangerously-skip-permissions |
|
" || CLAUDE_EXIT_CODE=$? |
|
fi |
|
|
|
# Check if we got any output from the --print method |
|
if [ -s "/tmp/claude_output_${ISSUE_NUMBER}.txt" ]; then |
|
log "Claude Code --print generated output, creating response file" |
|
|
|
# Create a response file with the generated content |
|
echo "# Claude Code Response for Issue #$ISSUE_NUMBER" > "CLAUDE_RESPONSE_${ISSUE_NUMBER}.md" |
|
echo "" >> "CLAUDE_RESPONSE_${ISSUE_NUMBER}.md" |
|
echo "**Original Request**: $ISSUE_COMMAND" >> "CLAUDE_RESPONSE_${ISSUE_NUMBER}.md" |
|
echo "" >> "CLAUDE_RESPONSE_${ISSUE_NUMBER}.md" |
|
echo "**Generated Response**:" >> "CLAUDE_RESPONSE_${ISSUE_NUMBER}.md" |
|
echo "" >> "CLAUDE_RESPONSE_${ISSUE_NUMBER}.md" |
|
cat "/tmp/claude_output_${ISSUE_NUMBER}.txt" >> "CLAUDE_RESPONSE_${ISSUE_NUMBER}.md" |
|
|
|
git add "CLAUDE_RESPONSE_${ISSUE_NUMBER}.md" |
|
|
|
# Clean up temp file |
|
rm -f "/tmp/claude_output_${ISSUE_NUMBER}.txt" |
|
|
|
# Reset exit code since we got output |
|
CLAUDE_EXIT_CODE=0 |
|
|
|
elif [ $CLAUDE_EXIT_CODE -ne 0 ]; then |
|
log "Claude Code execution failed with exit code: $CLAUDE_EXIT_CODE" |
|
|
|
# Determine the type of failure |
|
if [ $CLAUDE_EXIT_CODE -eq 124 ]; then |
|
FAILURE_REASON="Claude Code execution timed out" |
|
elif [ $CLAUDE_EXIT_CODE -eq 1 ]; then |
|
FAILURE_REASON="Claude Code encountered an error during execution" |
|
else |
|
FAILURE_REASON="Claude Code failed with exit code $CLAUDE_EXIT_CODE" |
|
fi |
|
|
|
# Create a detailed fallback implementation file |
|
echo "# Implementation Status for Issue #$ISSUE_NUMBER" > "IMPLEMENTATION_ISSUE_${ISSUE_NUMBER}.md" |
|
echo "" >> "IMPLEMENTATION_ISSUE_${ISSUE_NUMBER}.md" |
|
echo "**Original Request**: $ISSUE_COMMAND" >> "IMPLEMENTATION_ISSUE_${ISSUE_NUMBER}.md" |
|
echo "" >> "IMPLEMENTATION_ISSUE_${ISSUE_NUMBER}.md" |
|
echo "**Status**: $FAILURE_REASON" >> "IMPLEMENTATION_ISSUE_${ISSUE_NUMBER}.md" |
|
echo "**Timestamp**: $(date)" >> "IMPLEMENTATION_ISSUE_${ISSUE_NUMBER}.md" |
|
echo "**Branch**: \`$BRANCH_NAME\`" >> "IMPLEMENTATION_ISSUE_${ISSUE_NUMBER}.md" |
|
echo "" >> "IMPLEMENTATION_ISSUE_${ISSUE_NUMBER}.md" |
|
echo "**Next Steps**:" >> "IMPLEMENTATION_ISSUE_${ISSUE_NUMBER}.md" |
|
echo "- Check the logs in \`$LOG_FILE\`" >> "IMPLEMENTATION_ISSUE_${ISSUE_NUMBER}.md" |
|
echo "- Verify Claude Code is properly installed and configured" >> "IMPLEMENTATION_ISSUE_${ISSUE_NUMBER}.md" |
|
echo "- Consider manual implementation or re-running with different parameters" >> "IMPLEMENTATION_ISSUE_${ISSUE_NUMBER}.md" |
|
|
|
git add "IMPLEMENTATION_ISSUE_${ISSUE_NUMBER}.md" |
|
|
|
# Also notify in the issue comment |
|
add_issue_comment "⚠️ **Claude Code Execution Failed** |
|
|
|
$FAILURE_REASON |
|
|
|
I've created a status file on branch \`$BRANCH_NAME\` with details. Please check the logs or try manual implementation." |
|
|
|
# Clean up temp files |
|
rm -f "/tmp/claude_output_${ISSUE_NUMBER}.txt" |
|
fi |
|
|
|
# Clean up prompt file |
|
rm -f "$CLAUDE_PROMPT_FILE" |
|
|
|
# Check what changes were made |
|
log "Checking for changes..." |
|
if git diff --quiet && git diff --staged --quiet; then |
|
log "No changes were made by Claude Code" |
|
|
|
FINAL_COMMENT="⚠️ **Implementation Incomplete** |
|
|
|
I created a branch and ran Claude Code, but no changes were generated. The request was: |
|
|
|
> $ISSUE_COMMAND |
|
|
|
This might mean: |
|
- The request needs more specific details |
|
- Claude Code encountered an issue during execution |
|
- Manual implementation is required |
|
|
|
Please check the branch \`$BRANCH_NAME\` and provide more details if needed, or implement the changes manually." |
|
|
|
else |
|
log "Changes detected, committing and creating PR..." |
|
|
|
# Add all changes |
|
git add -A |
|
|
|
# Commit the changes |
|
git commit -m "Implement #$ISSUE_NUMBER: $ISSUE_TITLE |
|
|
|
$ISSUE_COMMAND |
|
|
|
🤖 Implemented locally with Claude Code" || { |
|
log "Error: Could not commit changes" |
|
add_issue_comment "❌ Error: Could not commit changes to branch \`$BRANCH_NAME\`." |
|
exit 1 |
|
} |
|
|
|
# Push the branch |
|
log "Pushing branch to origin" |
|
git push origin "$BRANCH_NAME" || { |
|
log "Error: Could not push branch $BRANCH_NAME" |
|
add_issue_comment "❌ Error: Could not push branch \`$BRANCH_NAME\` to GitHub. Please check permissions." |
|
exit 1 |
|
} |
|
|
|
# Create pull request |
|
log "Creating pull request" |
|
PR_URL=$(gh pr create \ |
|
--repo "$REPO" \ |
|
--title "Implement #$ISSUE_NUMBER: $ISSUE_TITLE" \ |
|
--body "Fixes #$ISSUE_NUMBER |
|
|
|
**Original Request**: $ISSUE_COMMAND |
|
|
|
**Implementation**: This PR was automatically generated by Claude Code running locally in response to the GitHub issue. |
|
|
|
🤖 Implemented locally with Claude Code" \ |
|
--base "$DEFAULT_BRANCH" \ |
|
--head "$BRANCH_NAME" 2>&1) || { |
|
log "Error creating pull request: $PR_URL" |
|
add_issue_comment "❌ Error: Could not create pull request. Branch \`$BRANCH_NAME\` has been pushed with changes." |
|
exit 1 |
|
} |
|
|
|
# Clean up Claude response file after successful PR creation |
|
if [ -f "CLAUDE_RESPONSE_${ISSUE_NUMBER}.md" ]; then |
|
log "Removing Claude response file after successful PR creation" |
|
git rm "CLAUDE_RESPONSE_${ISSUE_NUMBER}.md" |
|
git commit -m "Clean up Claude response file |
|
|
|
🤖 Automated cleanup after PR creation" || { |
|
log "Warning: Could not remove Claude response file" |
|
} |
|
|
|
# Push the cleanup commit |
|
git push origin "$BRANCH_NAME" || { |
|
log "Warning: Could not push cleanup commit" |
|
} |
|
fi |
|
|
|
FINAL_COMMENT="✅ **Implementation Complete!** |
|
|
|
I've successfully implemented the requested changes using Claude Code and created a pull request: |
|
|
|
**Pull Request**: $PR_URL |
|
**Branch**: \`$BRANCH_NAME\` |
|
|
|
The implementation has been completed locally and is ready for review. Please check the PR for details on what was implemented." |
|
fi |
|
|
|
add_issue_comment "$FINAL_COMMENT" |
|
log "Issue #$ISSUE_NUMBER processing completed successfully" |
|
|
|
exit 0 |