diff --git a/.agents/scripts/gh-failure-miner-helper.sh b/.agents/scripts/gh-failure-miner-helper.sh index 7bd283dc5..8681ca83d 100755 --- a/.agents/scripts/gh-failure-miner-helper.sh +++ b/.agents/scripts/gh-failure-miner-helper.sh @@ -283,8 +283,19 @@ extract_failed_events_json() { local checks_json checks_json=$(gh api "repos/${repo_slug}/commits/${head_sha}/check-runs?per_page=100" 2>/dev/null || printf '{"check_runs":[]}') + # Filter check runs for failures. Two-pass approach: + # 1. Hard failures (failure, cancelled, timed_out, startup_failure) — always collected + # 2. action_required — only from GitHub Actions runs. External apps (Codacy, SonarCloud) + # use action_required to mean "issues found for developer review", which is informational + # not a CI failure. Including these creates false systemic clusters (GH#4696). local failed_runs_json - failed_runs_json=$(printf '%s\n' "$checks_json" | jq '[.check_runs[] | select((.conclusion // "" | ascii_downcase) as $c | ["failure","cancelled","timed_out","action_required","startup_failure"] | index($c))]') + failed_runs_json=$(printf '%s\n' "$checks_json" | jq '[.check_runs[] | select( + ((.conclusion // "" | ascii_downcase) as $c | + ["failure","cancelled","timed_out","startup_failure"] | index($c)) + or + ((.conclusion // "" | ascii_downcase) == "action_required" + and (.app.slug // "" | ascii_downcase) == "github-actions") + )]') local failed_count failed_count=$(printf '%s\n' "$failed_runs_json" | jq 'length') @@ -306,10 +317,20 @@ extract_failed_events_json() { local run_id run_id=$(parse_run_id_from_details_url "$details_url") - local signature="not_collected" - if [[ "$include_logs" == "true" ]] && [[ -n "$run_id" ]] && [[ "$run_logs_checked" -lt "$max_run_logs" ]]; then + # For non-GitHub-Actions check runs (e.g., Codacy, SonarCloud), the details_url + # points to the external app, not a GH Actions run — so run_id is empty and logs + # can't be extracted. Use the conclusion as the signature instead of "not_collected" + # to produce meaningful cluster grouping (GH#4696). + local signature + if [[ -z "$run_id" ]]; then + local app_name + app_name=$(printf '%s\n' "$run_json" | jq -r '.app.name // "external"') + signature="${conclusion}:${app_name}" + elif [[ "$include_logs" == "true" ]] && [[ "$run_logs_checked" -lt "$max_run_logs" ]]; then signature=$(extract_failure_signature "$repo_slug" "$run_id") run_logs_checked=$((run_logs_checked + 1)) + else + signature="not_collected" fi jq -n \ diff --git a/.codacy.yml b/.codacy.yml index 9b0ec2e8e..c7142e1a3 100644 --- a/.codacy.yml +++ b/.codacy.yml @@ -3,9 +3,11 @@ # # Reference: https://docs.codacy.com/repositories-configure/codacy-configuration-file/ # -# Root cause context (GH#4346): +# Root cause context (GH#4346, GH#4696): # - Codacy flagged SC2086 (unquoted variable) in code that was being REMOVED by a PR fix. -# - Codacy also returned "not_collected" on a transient service issue. +# - "not_collected" reports were a failure-miner misclassification, not a Codacy issue. +# Codacy's action_required conclusion (= "issues found") was treated as a CI failure +# by gh-failure-miner-helper.sh. Fixed in GH#4696. # - This config excludes archived/ (same as CI shellcheck) and aligns tool settings. --- diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 84ea654cf..847aeddf4 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -175,16 +175,19 @@ jobs: echo "::warning::SONAR_TOKEN not configured — skipping SonarCloud scan" echo "Add SONAR_TOKEN to repository secrets to enable SonarCloud analysis" - - name: Codacy Analysis + - name: Codacy Integration Check + # Codacy analysis runs via the Codacy Production GitHub App (not this workflow). + # This step only verifies the API token is configured for Codacy API access. + # The "Codacy Static Code Analysis" check run is posted by the Codacy App + # independently on each PR commit. run: | if [[ -n "$CODACY_API_TOKEN" ]]; then - echo "Codacy Analysis Status..." - echo "Codacy API Token: Configured" - echo "Repository monitored at: https://app.codacy.com/gh/marcusquinn/aidevops" - echo "Codacy integration: Active" + echo "Codacy API token: configured" + echo "Codacy GitHub App: runs analysis independently on PRs" + echo "Dashboard: https://app.codacy.com/gh/marcusquinn/aidevops" else - echo "::notice::Codacy API token not configured, skipping analysis" - echo "Add CODACY_API_TOKEN to repository secrets to enable Codacy analysis" + echo "::notice::CODACY_API_TOKEN not configured — Codacy API access unavailable" + echo "Note: Codacy GitHub App analysis still runs if the app is installed" fi - name: Quality Analysis Summary @@ -192,15 +195,11 @@ jobs: echo "Code Quality Analysis Summary" echo "================================" if [[ -n "$SONAR_TOKEN" ]]; then - echo "SonarCloud: Analysis completed" + echo "SonarCloud: Analysis completed (via workflow action)" else echo "SonarCloud: Skipped (SONAR_TOKEN not configured)" fi - if [[ -n "$CODACY_API_TOKEN" ]]; then - echo "Codacy: Analysis completed" - else - echo "Codacy: Skipped (token not configured)" - fi + echo "Codacy: Runs via GitHub App (independent of this workflow)" echo "" echo "View Results:" echo " SonarCloud: https://sonarcloud.io/project/overview?id=marcusquinn_aidevops"