From 18f585f09158b6caa948046e1d76e416d6be1d46 Mon Sep 17 00:00:00 2001 From: Siddharth Balyan <52913345+alt-glitch@users.noreply.github.com> Date: Wed, 29 Apr 2026 00:01:58 +0530 Subject: [PATCH] ci(nix): auto-fix stale npm hashes on push to main (#16285) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci(nix): auto-fix stale npm hashes on push to main When a PR merges to main with updated package-lock.json or package.json in ui-tui/ or web/, the new auto-fix-main job detects stale npmDepsHash values and pushes a fix commit directly to main. This eliminates the recurring manual hash-bump PRs (#15420, #15314, #15272, #15244) by reusing the existing fix-lockfiles --apply pipeline. The fix commit only touches nix/*.nix files, which are outside the push path filter (package-lock.json / package.json), so it cannot re-trigger itself. Closes #15314 * fix(ci): use GitHub App token for auto-fix-main push GITHUB_TOKEN commits are invisible to workflow triggers (GitHub's infinite-loop prevention). The auto-fix-main job pushes directly to main, so the fix commit never triggered downstream nix.yml verification. Mint a short-lived token via the repo's GitHub App (daimon-nous, APP_ID + APP_PRIVATE_KEY secrets) so the push is treated as a real event and nix.yml fires to verify the corrected hashes. Tested via workflow_dispatch dry-run: app token minted successfully, checkout with app token succeeded, fix job correctly gated. Resolves review feedback from Bugbot (r3144569551). * ci(nix): rename lockfile check job for required status check Rename 'check' → 'nix-lockfile-check' so the status check name is unambiguous when added as a required check on main. * fix(ci): harden auto-fix-main against races, loops, and silent failures Address adversarial review findings: 1. Race condition (#1): Job-level concurrency with cancel-in-progress collapses back-to-back pushes; ref: main checkout always gets latest branch state; explicit push target (origin HEAD:main). 2. Loop prevention (#2): File-whitelist check before commit aborts if any file outside nix/{tui,web}.nix was modified, preventing accidental self-triggering. 3. Silent infra failures (#8): nix-lockfile-check now fails explicitly when fix-lockfiles exits without reporting stale status (catches nix setup failures, network errors, script bugs that bypass continue-on-error). 4. Commit traceability (#11): Auto-fix commits include source SHA and workflow run URL in the commit body. 5. Explicit push target (#12): git push origin HEAD:main instead of bare git push. --------- Co-authored-by: alt-glitch --- .github/workflows/nix-lockfile-check.yml | 8 +- .github/workflows/nix-lockfile-fix.yml | 105 ++++++++++++++++++++++- nix/lib.nix | 5 +- 3 files changed, 114 insertions(+), 4 deletions(-) diff --git a/.github/workflows/nix-lockfile-check.yml b/.github/workflows/nix-lockfile-check.yml index 9c9bc734a6..6326f45f9a 100644 --- a/.github/workflows/nix-lockfile-check.yml +++ b/.github/workflows/nix-lockfile-check.yml @@ -13,7 +13,7 @@ concurrency: cancel-in-progress: true jobs: - check: + nix-lockfile-check: runs-on: ubuntu-latest timeout-minutes: 20 steps: @@ -36,6 +36,12 @@ jobs: LINK_SHA: ${{ steps.sha.outputs.full }} run: nix run .#fix-lockfiles -- --check + - name: Fail if check crashed without reporting + if: steps.check.outputs.stale != 'true' && steps.check.outputs.stale != 'false' + run: | + echo "::error::fix-lockfiles exited without reporting stale status — likely an infrastructure or script failure" + exit 1 + - name: Post sticky PR comment (stale) if: steps.check.outputs.stale == 'true' && github.event_name == 'pull_request' uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2.9.1 diff --git a/.github/workflows/nix-lockfile-fix.yml b/.github/workflows/nix-lockfile-fix.yml index a1c7dd6e5c..5238660d04 100644 --- a/.github/workflows/nix-lockfile-fix.yml +++ b/.github/workflows/nix-lockfile-fix.yml @@ -1,6 +1,13 @@ name: Nix Lockfile Fix on: + push: + branches: [main] + paths: + - 'ui-tui/package-lock.json' + - 'ui-tui/package.json' + - 'web/package-lock.json' + - 'web/package.json' workflow_dispatch: inputs: pr_number: @@ -19,9 +26,103 @@ concurrency: cancel-in-progress: false jobs: + # ── Auto-fix on main ─────────────────────────────────────────────── + # Fires when a push to main touches package.json or package-lock.json + # in ui-tui/ or web/. Runs fix-lockfiles --apply and pushes the hash + # update commit directly to main so Nix builds never stay broken. + # + # Safety invariants: + # 1. The fix commit only touches nix/*.nix files, which are NOT in + # the paths filter above, so this cannot re-trigger itself. + # 2. An explicit file-whitelist check before commit aborts if + # fix-lockfiles ever modifies unexpected files. + # 3. Job-level concurrency with cancel-in-progress: true ensures + # back-to-back pushes collapse to the newest; ref: main checkout + # always operates on the latest branch state. + # 4. Uses a GitHub App token (not GITHUB_TOKEN) so the fix commit + # triggers downstream nix.yml verification. + auto-fix-main: + if: github.event_name == 'push' + runs-on: ubuntu-latest + timeout-minutes: 25 + concurrency: + group: auto-fix-main + cancel-in-progress: true + steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@7bfa3a4717ef143a604ee0a99d859b8886a96d00 # v1.9.3 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + ref: main + token: ${{ steps.app-token.outputs.token }} + + - uses: ./.github/actions/nix-setup + + - name: Apply lockfile hashes + id: apply + run: nix run .#fix-lockfiles -- --apply + + - name: Commit & push + if: steps.apply.outputs.changed == 'true' + shell: bash + run: | + set -euo pipefail + + # Ensure only nix files were modified — prevents accidental + # self-triggering if fix-lockfiles ever touches package files. + unexpected="$(git diff --name-only | grep -Ev '^nix/(tui|web)\.nix$' || true)" + if [ -n "$unexpected" ]; then + echo "::error::Unexpected modified files: $unexpected" + exit 1 + fi + + # Record the base SHA before committing — used to detect package + # file changes if we need to rebase after a non-fast-forward push. + BASE_SHA="$(git rev-parse HEAD)" + + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add nix/tui.nix nix/web.nix + git commit -m "fix(nix): auto-refresh npm lockfile hashes" \ + -m "Source: $GITHUB_SHA" \ + -m "Run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" + + # Retry push with rebase in case main advanced with an unrelated + # commit during the nix build. Without this, a non-fast-forward + # rejection silently loses the fix. If package files changed during + # the rebase, abort — a fresh auto-fix run will handle the new state. + for attempt in 1 2 3; do + if git push origin HEAD:main; then + exit 0 + fi + echo "::warning::Push attempt $attempt failed (non-fast-forward?), rebasing…" + git fetch origin main + + # If package files changed between our base and the new main, + # our computed hashes are stale. Abort and let the next triggered + # run recompute from the correct package-lock state. + pkg_changed="$(git diff --name-only "$BASE_SHA"..origin/main -- \ + 'ui-tui/package-lock.json' 'ui-tui/package.json' \ + 'web/package-lock.json' 'web/package.json' || true)" + if [ -n "$pkg_changed" ]; then + echo "::warning::Package files changed since hash computation — aborting; a fresh run will recompute" + exit 0 + fi + + git rebase origin/main + done + echo "::error::Failed to push after 3 rebase attempts" + exit 1 + + # ── PR fix (manual / checkbox) ───────────────────────────────────── + # Existing behavior: run on manual dispatch OR when a task-list + # checkbox in the sticky lockfile-check comment flips from [ ] to [x]. fix: - # Run on manual dispatch OR when a task-list checkbox in the sticky - # lockfile-check comment flips from `[ ]` to `[x]`. if: | github.event_name == 'workflow_dispatch' || (github.event_name == 'issue_comment' diff --git a/nix/lib.nix b/nix/lib.nix index e53a989f85..226eb912da 100644 --- a/nix/lib.nix +++ b/nix/lib.nix @@ -187,7 +187,10 @@ if [ "$MODE" = "--apply" ]; then sed -i "s|hash = \"sha256-[^\"]*\";|hash = \"$NEW_HASH\";|" "$NIX_FILE" - nix build ".#$ATTR.npmDeps" --no-link --print-build-logs + if ! nix build ".#$ATTR.npmDeps" --no-link --print-build-logs; then + echo " verification build failed after hash update" >&2 + exit 1 + fi FIXED=1 echo " fixed" fi