mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-01 01:51:44 +00:00
* 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 <alt-glitch@users.noreply.github.com>
250 lines
10 KiB
YAML
250 lines
10 KiB
YAML
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:
|
|
description: 'PR number to fix (leave empty to run on the selected branch)'
|
|
required: false
|
|
type: string
|
|
issue_comment:
|
|
types: [edited]
|
|
|
|
permissions:
|
|
contents: write
|
|
pull-requests: write
|
|
|
|
concurrency:
|
|
group: nix-lockfile-fix-${{ github.event.issue.number || github.event.inputs.pr_number || github.ref }}
|
|
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:
|
|
if: |
|
|
github.event_name == 'workflow_dispatch' ||
|
|
(github.event_name == 'issue_comment'
|
|
&& github.event.issue.pull_request != null
|
|
&& contains(github.event.comment.body, '[x] **Apply lockfile fix**')
|
|
&& !contains(github.event.changes.body.from, '[x] **Apply lockfile fix**'))
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 25
|
|
steps:
|
|
- name: Authorize & resolve PR
|
|
id: resolve
|
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
|
with:
|
|
script: |
|
|
// 1. Verify the actor has write access — applies to both checkbox
|
|
// clicks and manual dispatch.
|
|
const { data: perm } =
|
|
await github.rest.repos.getCollaboratorPermissionLevel({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
username: context.actor,
|
|
});
|
|
if (!['admin', 'write', 'maintain'].includes(perm.permission)) {
|
|
core.setFailed(
|
|
`${context.actor} lacks write access (has: ${perm.permission})`
|
|
);
|
|
return;
|
|
}
|
|
|
|
// 2. Resolve which ref to check out.
|
|
let prNumber = '';
|
|
if (context.eventName === 'issue_comment') {
|
|
prNumber = String(context.payload.issue.number);
|
|
} else if (context.eventName === 'workflow_dispatch') {
|
|
prNumber = context.payload.inputs.pr_number || '';
|
|
}
|
|
|
|
if (!prNumber) {
|
|
core.setOutput('ref', context.ref.replace(/^refs\/heads\//, ''));
|
|
core.setOutput('repo', context.repo.repo);
|
|
core.setOutput('owner', context.repo.owner);
|
|
core.setOutput('pr', '');
|
|
return;
|
|
}
|
|
|
|
const { data: pr } = await github.rest.pulls.get({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: Number(prNumber),
|
|
});
|
|
core.setOutput('ref', pr.head.ref);
|
|
core.setOutput('repo', pr.head.repo.name);
|
|
core.setOutput('owner', pr.head.repo.owner.login);
|
|
core.setOutput('pr', String(pr.number));
|
|
|
|
# Wipe the sticky lockfile-check comment to a "running" state as soon
|
|
# as the job is authorized, so the user sees their click was picked up
|
|
# before the ~minute of nix build work.
|
|
- name: Mark sticky as running
|
|
if: steps.resolve.outputs.pr != ''
|
|
uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2.9.1
|
|
with:
|
|
header: nix-lockfile-check
|
|
number: ${{ steps.resolve.outputs.pr }}
|
|
message: |
|
|
### 🔄 Applying lockfile fix…
|
|
|
|
Triggered by @${{ github.actor }} — [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).
|
|
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
with:
|
|
repository: ${{ steps.resolve.outputs.owner }}/${{ steps.resolve.outputs.repo }}
|
|
ref: ${{ steps.resolve.outputs.ref }}
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
fetch-depth: 0
|
|
|
|
- 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
|
|
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): refresh npm lockfile hashes"
|
|
git push
|
|
|
|
- name: Update sticky (applied)
|
|
if: steps.apply.outputs.changed == 'true' && steps.resolve.outputs.pr != ''
|
|
uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2.9.1
|
|
with:
|
|
header: nix-lockfile-check
|
|
number: ${{ steps.resolve.outputs.pr }}
|
|
message: |
|
|
### ✅ Lockfile fix applied
|
|
|
|
Pushed a commit refreshing the npm lockfile hashes — [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).
|
|
|
|
- name: Update sticky (already current)
|
|
if: steps.apply.outputs.changed == 'false' && steps.resolve.outputs.pr != ''
|
|
uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2.9.1
|
|
with:
|
|
header: nix-lockfile-check
|
|
number: ${{ steps.resolve.outputs.pr }}
|
|
message: |
|
|
### ✅ Lockfile hashes already current
|
|
|
|
Nothing to commit — [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).
|
|
|
|
- name: Update sticky (failed)
|
|
if: failure() && steps.resolve.outputs.pr != ''
|
|
uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2.9.1
|
|
with:
|
|
header: nix-lockfile-check
|
|
number: ${{ steps.resolve.outputs.pr }}
|
|
message: |
|
|
### ❌ Lockfile fix failed
|
|
|
|
See the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for logs.
|