name: Issue Analysis on: issues: types: [opened] workflow_dispatch: inputs: issueNumber: description: 'The number of the issue to analyze manually' required: true type: string jobs: check-issue: runs-on: ubuntu-latest permissions: contents: read issues: write env: # If triggered by 'issues', it uses github.event.issue.number. # If triggered by 'workflow_dispatch', it uses the number you provided in the form. ISSUE_NUMBER: ${{ github.event.issue.number || inputs.issueNumber }} IGNORE_BOT_NAMES_JSON: '["ellipsis-dev"]' steps: - name: Checkout repository uses: actions/checkout@v4 - name: Bot Setup id: setup uses: ./.github/actions/bot-setup with: bot-app-id: ${{ secrets.BOT_APP_ID }} bot-private-key: ${{ secrets.BOT_PRIVATE_KEY }} opencode-api-key: ${{ secrets.OPENCODE_API_KEY }} opencode-model: ${{ secrets.OPENCODE_MODEL }} opencode-fast-model: ${{ secrets.OPENCODE_FAST_MODEL }} custom-providers-json: ${{ secrets.CUSTOM_PROVIDERS_JSON }} - name: Add reaction to issue env: GH_TOKEN: ${{ steps.setup.outputs.token }} run: | gh api \ --method POST \ -H "Accept: application/vnd.github+json" \ /repos/${{ github.repository }}/issues/${{ env.ISSUE_NUMBER }}/reactions \ -f content='eyes' - name: Save secure prompt from base branch run: cp .github/prompts/issue-comment.md /tmp/issue-comment.md - name: Checkout repository uses: actions/checkout@v4 with: token: ${{ steps.setup.outputs.token }} fetch-depth: 0 # Full history needed for git log, git blame, and other investigation commands - name: Fetch and Format Full Issue Context id: issue_details env: GH_TOKEN: ${{ steps.setup.outputs.token }} run: | # Fetch all necessary data in one call issue_data=$(gh issue view ${{ env.ISSUE_NUMBER }} --json author,title,body,createdAt,state,comments) timeline_data=$(gh api "/repos/${{ github.repository }}/issues/${{ env.ISSUE_NUMBER }}/timeline") # Debug: Output issue_data and timeline_data for inspection echo "$issue_data" > issue_data.txt echo "$timeline_data" > timeline_data.txt # Prepare metadata author=$(echo "$issue_data" | jq -r .author.login) created_at=$(echo "$issue_data" | jq -r .createdAt) state=$(echo "$issue_data" | jq -r .state) title=$(echo "$issue_data" | jq -r .title) body=$(echo "$issue_data" | jq -r '.body // "(No description provided)"') # Prepare comments (exclude ignored bots) total_issue_comments=$(echo "$issue_data" | jq '((.comments // []) | length)') echo "Debug: total issue comments before filtering = $total_issue_comments" comments_filter_err=$(mktemp 2>/dev/null || echo "/tmp/issue_comments_filter_err.log") if comments=$(echo "$issue_data" | jq -r --argjson ignored "$IGNORE_BOT_NAMES_JSON" 'if (((.comments // []) | length) > 0) then ((.comments[]? | select((.author.login as $login | $ignored | index($login)) | not)) | "- " + (.author.login // "unknown") + " at " + (.createdAt // "N/A") + ":\n" + ((.body // "") | tostring) + "\n") else "No comments have been posted yet." end' 2>"$comments_filter_err"); then filtered_comments=$(echo "$comments" | grep -c "^- " || true) filtered_comments=${filtered_comments//[^0-9]/} [ -z "$filtered_comments" ] && filtered_comments=0 total_issue_comments=${total_issue_comments//[^0-9]/} [ -z "$total_issue_comments" ] && total_issue_comments=0 excluded_comments=$(( total_issue_comments - filtered_comments )) || excluded_comments=0 echo "✓ Filtered comments: $filtered_comments included, $excluded_comments excluded (ignored bots)" if [ -s "$comments_filter_err" ]; then echo "::debug::jq stderr (issue comments) emitted output:" cat "$comments_filter_err" fi else jq_status=$? echo "::warning::Issue comment filtering failed (exit $jq_status), using unfiltered data" if [ -s "$comments_filter_err" ]; then echo "::warning::jq stderr (issue comments):" cat "$comments_filter_err" else echo "::warning::jq returned no stderr for issue comment filter" fi comments=$(echo "$issue_data" | jq -r 'if (((.comments // []) | length) > 0) then ((.comments[]?) | "- " + (.author.login // "unknown") + " at " + (.createdAt // "N/A") + ":\n" + ((.body // "") | tostring) + "\n") else "No comments have been posted yet." end') excluded_comments=0 echo "FILTER_ERROR_COMMENTS=true" >> $GITHUB_ENV fi rm -f "$comments_filter_err" || true # Prepare cross-references references=$(echo "$timeline_data" | jq -r '.[] | select(.event == "cross-referenced") | .source.issue | "- Mentioned in \(.html_url | if contains("/pull/") then "PR" else "Issue" end): #\(.number) - \(.title)"') if [ -z "$references" ]; then references="No other issues or PRs have mentioned this thread." fi # Define a unique, random delimiter for the main context block CONTEXT_DELIMITER="GH_ISSUE_CONTEXT_DELIMITER_$(openssl rand -hex 8)" # Assemble the final context block directly into the environment file line by line echo "ISSUE_CONTEXT<<$CONTEXT_DELIMITER" >> "$GITHUB_ENV" echo "Issue: #${{ env.ISSUE_NUMBER }}" >> "$GITHUB_ENV" echo "Title: $title" >> "$GITHUB_ENV" echo "Author: $author" >> "$GITHUB_ENV" echo "Created At: $created_at" >> "$GITHUB_ENV" echo "State: $state" >> "$GITHUB_ENV" echo "" >> "$GITHUB_ENV" echo "$body" >> "$GITHUB_ENV" echo "" >> "$GITHUB_ENV" echo "" >> "$GITHUB_ENV" echo "$comments" >> "$GITHUB_ENV" echo "" >> "$GITHUB_ENV" echo "" >> "$GITHUB_ENV" echo "$references" >> "$GITHUB_ENV" echo "" >> "$GITHUB_ENV" echo "$CONTEXT_DELIMITER" >> "$GITHUB_ENV" # Also export author for the acknowledgment comment echo "ISSUE_AUTHOR=$author" >> $GITHUB_ENV - name: Analyze issue and suggest resolution env: GITHUB_TOKEN: ${{ steps.setup.outputs.token }} ISSUE_CONTEXT: ${{ env.ISSUE_CONTEXT }} ISSUE_NUMBER: ${{ env.ISSUE_NUMBER }} ISSUE_AUTHOR: ${{ env.ISSUE_AUTHOR }} OPENCODE_PERMISSION: | { "bash": { "gh*": "allow", "git*": "allow", "jq*": "allow" }, "webfetch": "deny" } run: | # Only substitute the variables we intend; leave example $vars and secrets intact VARS='${ISSUE_CONTEXT} ${ISSUE_NUMBER} ${ISSUE_AUTHOR}' envsubst "$VARS" < /tmp/issue-comment.md | opencode run --share -