From 565287528dab408538ba75eef68ca3df3d879c5d Mon Sep 17 00:00:00 2001 From: ivangegovdve-sudo Date: Sat, 1 Aug 2026 20:31:49 +0300 Subject: [PATCH] fix(ci): stop CI Report dying silently when the tests job fails (#2728) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(ci): stop CI Report dying silently when the tests job fails The "Build report" step in ci-report.yml runs under `bash --noprofile --norc -e -o pipefail`. It located its inputs with UNIT_SUMMARY=$(find "$DIR/test-reports" -name ... 2>/dev/null | head -1) `coverage-merge` in ci-tests.yml is `needs: tests` with no `if: always()`, so any failing shard skips it and the `test-reports` artifact is never uploaded. `find` then runs against a directory that does not exist and exits 1; `-o pipefail` carries that status through `| head -1`, the command substitution hands it to the assignment, and `-e` kills the step. The death is invisible: `2>/dev/null` discards find's error and the whole report is built into `$GITHUB_OUTPUT`, so the step logs nothing and just reports "Process completed with exit code 1". "Comment on PR" is then skipped, so the CI Report workflow fails and posts nothing on exactly the PRs whose tests failed — when the report is most useful. The "Coverage data unavailable" fallback already existed for this case but was unreachable, because the script died ~160 lines before it. Route the four lookups through a `find_first` helper that returns empty when the root is absent. Verified by extracting the step body and running it against both artifact layouts: with `test-reports` present the output is byte-identical to the previous script (1335 bytes), and with it absent the step now exits 0 and emits the coverage-unavailable report instead of exiting 1 with an empty $GITHUB_OUTPUT. Observed on 32 of the last 100 failed runs; correlation with the tests job's conclusion was 6/6 failure and 4/4 success in the sampled runs. Co-Authored-By: Claude Opus 5 (1M context) * fix(ci): let the prebuild assertion report a missing .node `Build prebuild` deletes `$pkgdir/prebuilds` before running prebuildify, so a run that emits nothing without failing leaves `find` searching a path that no longer exists. Under the step's `shell: bash` (`-e -o pipefail`) that `find` exits 1 and kills the step before the `test -n "$out"` guard below it — the guard written to explain exactly this case never runs, and the job dies with a bare "Process completed with exit code 1". Same shape as the `ci-report.yml` fix in this PR: a lookup that exits non-zero on an absent root pre-empts the fallback beneath it. `|| true` hands the empty result to the guard, which still fails the build, now with `::error::prebuildify produced no .node`. Co-Authored-By: Claude Opus 5 (1M context) --------- Co-authored-by: Claude Opus 5 (1M context) Co-authored-by: Gergő Magyar Co-authored-by: Gergo Magyar --- .../workflows/build-tree-sitter-prebuilds.yml | 8 ++++++- .github/workflows/ci-report.yml | 24 +++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-tree-sitter-prebuilds.yml b/.github/workflows/build-tree-sitter-prebuilds.yml index 06e2b4c9f..d992c5634 100644 --- a/.github/workflows/build-tree-sitter-prebuilds.yml +++ b/.github/workflows/build-tree-sitter-prebuilds.yml @@ -414,7 +414,13 @@ jobs: # prebuilds/-/.node. ( cd "$pkgdir" && npx --no-install prebuildify --napi --strip ) - out=$(find "$pkgdir/prebuilds" -name '*.node' -print -quit) + # `|| true` so the `test -n` below is the thing that reports a missing + # prebuild. `rm -rf` above deletes the directory, so a prebuildify + # run that emits nothing without failing leaves `find` searching a + # path that no longer exists — it exits 1 and `-e` would kill the step + # before the `::error::` line, which is exactly the case that line + # exists to explain. + out=$(find "$pkgdir/prebuilds" -name '*.node' -print -quit || true) test -n "$out" || { echo "::error::prebuildify produced no .node"; exit 1; } produced=$(basename "$(dirname "$out")") [ "$produced" = "$PLATFORM_ARCH" ] || { echo "::error::built $produced, expected $PLATFORM_ARCH"; exit 1; } diff --git a/.github/workflows/ci-report.yml b/.github/workflows/ci-report.yml index 6a55254ca..f1afd43b7 100644 --- a/.github/workflows/ci-report.yml +++ b/.github/workflows/ci-report.yml @@ -256,21 +256,37 @@ jobs: fi } + # ── Helper: first matching file, tolerating an absent root ── + # `coverage-merge` (ci-tests.yml) is `needs: tests` with no + # `if: always()`, so a failing shard skips it and the `test-reports` + # artifact is never uploaded. A bare `find` on the missing directory + # exits 1; `-o pipefail` carries that through `| head -1` and `-e` + # then killed this step — silently, because stderr is discarded and + # stdout is redirected to $GITHUB_OUTPUT. That skipped "Comment on + # PR" and failed the run precisely when a PR had failing tests, which + # is when the report matters most. Degrade to "" instead so the + # coverage-unavailable fallback below can do its job. + find_first() { + local root=$1 name=$2 + [ -d "$root" ] || return 0 + find "$root" -name "$name" -type f 2>/dev/null | head -1 || true + } + # ── Read coverage reports ── - UNIT_SUMMARY=$(find "$DIR/test-reports" -name "coverage-summary.json" -type f 2>/dev/null | head -1) + UNIT_SUMMARY=$(find_first "$DIR/test-reports" "coverage-summary.json") read_cov "U" "$UNIT_SUMMARY" # ── Read base branch coverage (main) ── BASE_SUMMARY="" if [ "$BASE_FOUND" = "true" ] && [ -n "$BASE_DIR" ]; then - BASE_SUMMARY=$(find "$BASE_DIR/base" -name "coverage-summary.json" -type f 2>/dev/null | head -1) + BASE_SUMMARY=$(find_first "$BASE_DIR/base" "coverage-summary.json") fi read_cov "B" "$BASE_SUMMARY" # ── Locate test results ── - RESULTS_FILE=$(find "$DIR/test-reports" -name "test-results.json" -type f 2>/dev/null | head -1) - WEB_RESULTS_FILE=$(find "$DIR/test-reports" -name "web-test-results.json" -type f 2>/dev/null | head -1) + RESULTS_FILE=$(find_first "$DIR/test-reports" "test-results.json") + WEB_RESULTS_FILE=$(find_first "$DIR/test-reports" "web-test-results.json") sum_results() { local file=$1