mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-06 02:37:05 +08:00
Compare commits
3 Commits
dependabot
...
fix/nix-lo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf71d4d0f7 | ||
|
|
5d6e9d2087 | ||
|
|
41af3a5078 |
57
.github/workflows/nix-lockfile-fix.yml
vendored
57
.github/workflows/nix-lockfile-fix.yml
vendored
@@ -26,27 +26,62 @@ concurrency:
|
|||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# ── Auto-fix on main ───────────────────────────────────────────────
|
# ── Check on main ──────────────────────────────────────────────────
|
||||||
# Fires when a push to main touches package.json or package-lock.json
|
# Fires on every push to main that touches a path-filtered file. Only
|
||||||
# in ui-tui/ or web/. Runs fix-lockfiles and pushes the hash
|
# runs fix-lockfiles --check and emits stale=true/false as a job output.
|
||||||
# update commit directly to main so Nix builds never stay broken.
|
# NEVER cancels in-progress siblings: each push's detection signal must
|
||||||
|
# run to completion, otherwise a rapid-fire sequence of pushes can drop
|
||||||
|
# the only signal that ever existed for a given lockfile delta and
|
||||||
|
# leave main quietly broken.
|
||||||
|
check-main:
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 20
|
||||||
|
concurrency:
|
||||||
|
group: nix-lockfile-check-main
|
||||||
|
cancel-in-progress: false
|
||||||
|
outputs:
|
||||||
|
stale: ${{ steps.check.outputs.stale }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
|
||||||
|
- uses: ./.github/actions/nix-setup
|
||||||
|
with:
|
||||||
|
cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
|
||||||
|
- name: Check lockfile hashes
|
||||||
|
id: check
|
||||||
|
# --check is non-fatal here: it sets $GITHUB_OUTPUT.stale and
|
||||||
|
# exits 1 when drift is found. We want stale=true/false to
|
||||||
|
# propagate to apply-main as a job output, not to fail this job.
|
||||||
|
run: nix run .#fix-lockfiles -- --check || true
|
||||||
|
|
||||||
|
# ── Apply on main ──────────────────────────────────────────────────
|
||||||
|
# Depends on check-main. Only runs when stale=true, so the cheap
|
||||||
|
# detection path isn't gated behind the expensive apply path.
|
||||||
|
# DOES cancel in-progress siblings: we only want the newest push's
|
||||||
|
# fix to actually land on main — older in-flight applies are
|
||||||
|
# computing against outdated state and would race the push anyway.
|
||||||
#
|
#
|
||||||
# Safety invariants:
|
# Safety invariants:
|
||||||
# 1. The fix commit only touches nix/*.nix files, which are NOT in
|
# 1. The fix commit only touches nix/*.nix files, which are NOT in
|
||||||
# the paths filter above, so this cannot re-trigger itself.
|
# the paths filter above, so this cannot re-trigger itself.
|
||||||
# 2. An explicit file-whitelist check before commit aborts if
|
# 2. An explicit file-whitelist check before commit aborts if
|
||||||
# fix-lockfiles ever modifies unexpected files.
|
# fix-lockfiles ever modifies unexpected files.
|
||||||
# 3. Job-level concurrency with cancel-in-progress: true ensures
|
# 3. Uses a GitHub App token (not GITHUB_TOKEN) so the fix commit
|
||||||
# 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.
|
# triggers downstream nix.yml verification.
|
||||||
auto-fix-main:
|
# 4. Rebase-retry-on-push handles the case where main advanced
|
||||||
if: github.event_name == 'push'
|
# with an unrelated commit during the nix build; aborts
|
||||||
|
# cleanly if package files changed since hash computation.
|
||||||
|
apply-main:
|
||||||
|
needs: check-main
|
||||||
|
if: needs.check-main.outputs.stale == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 25
|
timeout-minutes: 25
|
||||||
concurrency:
|
concurrency:
|
||||||
group: auto-fix-main
|
group: nix-lockfile-apply-main
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
steps:
|
steps:
|
||||||
- name: Generate GitHub App token
|
- name: Generate GitHub App token
|
||||||
|
|||||||
109
nix/lib.nix
109
nix/lib.nix
@@ -160,32 +160,91 @@
|
|||||||
FIXED=0
|
FIXED=0
|
||||||
REPORT=""
|
REPORT=""
|
||||||
|
|
||||||
|
# Normalize trailing newlines so a source-vs-cached lockfile diff is
|
||||||
|
# purely content-driven. Matches the patchPhase normalization in
|
||||||
|
# mkNpmPassthru, so this mirrors exactly what npmConfigHook sees.
|
||||||
|
_norm_lockfile() { sed -z 's/\n*$/\n/' "$1"; }
|
||||||
|
|
||||||
|
# Force a clean FOD rebuild to surface the real `got:` hash from the
|
||||||
|
# actual source lockfile, bypassing cachix pinning. Echoes the new
|
||||||
|
# hash on success; exits the loop entry with an error on failure.
|
||||||
|
# The NIX_FILE is always restored, even if the rebuild errors out.
|
||||||
|
_recompute_hash_from_wipe() {
|
||||||
|
local nix_file="$1" attr="$2"
|
||||||
|
local bak
|
||||||
|
bak="$(mktemp)"
|
||||||
|
cp "$nix_file" "$bak"
|
||||||
|
sed -i 's|hash = "sha256-[^"]*";|hash = "";|' "$nix_file"
|
||||||
|
local out rc
|
||||||
|
out=$(nix build ".#$attr.npmDeps" --no-link 2>&1) && rc=0 || rc=$?
|
||||||
|
cp "$bak" "$nix_file"
|
||||||
|
rm -f "$bak"
|
||||||
|
local new_hash
|
||||||
|
new_hash=$(echo "$out" | awk '/got:/ {print $2; exit}')
|
||||||
|
if [ -z "$new_hash" ]; then
|
||||||
|
echo "$out" | tail -20 >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "$new_hash"
|
||||||
|
}
|
||||||
|
|
||||||
for entry in "''${ENTRIES[@]}"; do
|
for entry in "''${ENTRIES[@]}"; do
|
||||||
IFS=":" read -r ATTR FOLDER NIX_FILE <<< "$entry"
|
IFS=":" read -r ATTR FOLDER NIX_FILE <<< "$entry"
|
||||||
echo "==> .#$ATTR ($FOLDER -> $NIX_FILE)"
|
echo "==> .#$ATTR ($FOLDER -> $NIX_FILE)"
|
||||||
OUTPUT=$(nix build ".#$ATTR.npmDeps" --no-link --print-build-logs 2>&1)
|
|
||||||
STATUS=$?
|
|
||||||
if [ "$STATUS" -eq 0 ]; then
|
|
||||||
echo " ok"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
NEW_HASH=$(echo "$OUTPUT" | awk '/got:/ {print $2; exit}')
|
# Build npmDeps (may resolve a cached FOD artifact pinned to a
|
||||||
if [ -z "$NEW_HASH" ]; then
|
# prior lockfile) and capture the resulting store path.
|
||||||
# Magic-Nix-Cache occasionally returns HTTP 418 / cache-throttled
|
OUTPUT=$(nix build ".#$ATTR.npmDeps" --no-link --print-out-paths --print-build-logs 2>&1)
|
||||||
# mid-run; nix then prints "outputs … not valid, so checking is
|
STATUS=$?
|
||||||
# not possible" without a `got:` line. That's an infrastructure
|
|
||||||
# blip, not a stale lockfile — warn + skip rather than failing
|
NEW_HASH=""
|
||||||
# the lint. A real hash mismatch would still surface in the
|
|
||||||
# primary `.#$ATTR` build, which is a separate CI job.
|
if [ "$STATUS" -eq 0 ]; then
|
||||||
if echo "$OUTPUT" | grep -qE "throttled|HTTP error 418|substituter .* is disabled|some outputs of .* are not valid"; then
|
# --print-out-paths emits the store path(s) on stdout (merged
|
||||||
echo " skipped (transient cache failure — see primary nix build for real status)" >&2
|
# with build logs here). Last non-empty line is the npmDeps path.
|
||||||
echo "$OUTPUT" | tail -8 >&2
|
OUT_PATH=$(echo "$OUTPUT" | awk 'NF' | tail -1)
|
||||||
|
SRC_LOCK="$FOLDER/package-lock.json"
|
||||||
|
CACHED_LOCK="$OUT_PATH/package-lock.json"
|
||||||
|
if [ ! -f "$CACHED_LOCK" ]; then
|
||||||
|
echo " unexpected: $CACHED_LOCK missing from npmDeps output" >&2
|
||||||
|
echo "$OUTPUT" | tail -20 >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if diff -q <(_norm_lockfile "$SRC_LOCK") <(_norm_lockfile "$CACHED_LOCK") >/dev/null 2>&1; then
|
||||||
|
echo " ok"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
echo " build failed with no hash mismatch:" >&2
|
# The pinned FOD hash resolves (cachix handed us a cached
|
||||||
echo "$OUTPUT" | tail -40 >&2
|
# artifact), but the lockfile inside that artifact no longer
|
||||||
exit 1
|
# matches the source lockfile. npmConfigHook in the real .#$ATTR
|
||||||
|
# build rejects this — so treat as stale and recompute the hash
|
||||||
|
# against the current source lockfile.
|
||||||
|
echo " lockfile drift — source $SRC_LOCK differs from $CACHED_LOCK"
|
||||||
|
if ! NEW_HASH=$(_recompute_hash_from_wipe "$NIX_FILE" "$ATTR"); then
|
||||||
|
echo " failed to recompute hash after wipe+rebuild" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# npmDeps build failed outright (no cached artifact for the
|
||||||
|
# pinned hash; nix attempted to re-run fetchNpmDeps and reported
|
||||||
|
# a hash mismatch). Parse got: from the error.
|
||||||
|
NEW_HASH=$(echo "$OUTPUT" | awk '/got:/ {print $2; exit}')
|
||||||
|
if [ -z "$NEW_HASH" ]; then
|
||||||
|
# Magic-Nix-Cache occasionally returns HTTP 418 / cache-throttled
|
||||||
|
# mid-run; nix then prints "outputs … not valid, so checking is
|
||||||
|
# not possible" without a `got:` line. That's an infrastructure
|
||||||
|
# blip, not a stale lockfile — warn + skip rather than failing
|
||||||
|
# the lint. A real hash mismatch would still surface in the
|
||||||
|
# primary `.#$ATTR` build, which is a separate CI job.
|
||||||
|
if echo "$OUTPUT" | grep -qE "throttled|HTTP error 418|substituter .* is disabled|some outputs of .* are not valid"; then
|
||||||
|
echo " skipped (transient cache failure — see primary nix build for real status)" >&2
|
||||||
|
echo "$OUTPUT" | tail -8 >&2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo " build failed with no hash mismatch:" >&2
|
||||||
|
echo "$OUTPUT" | tail -40 >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
HASH_LINE=$(grep -n 'hash = "sha256-' "$NIX_FILE" | head -1 | cut -d: -f1)
|
HASH_LINE=$(grep -n 'hash = "sha256-' "$NIX_FILE" | head -1 | cut -d: -f1)
|
||||||
@@ -205,12 +264,16 @@
|
|||||||
|
|
||||||
if [ "$MODE" = "--apply" ]; then
|
if [ "$MODE" = "--apply" ]; then
|
||||||
sed -i "s|hash = \"sha256-[^\"]*\";|hash = \"$NEW_HASH\";|" "$NIX_FILE"
|
sed -i "s|hash = \"sha256-[^\"]*\";|hash = \"$NEW_HASH\";|" "$NIX_FILE"
|
||||||
if ! nix build ".#$ATTR.npmDeps" --no-link --print-build-logs; then
|
# Verify with the REAL package build (not just .npmDeps). This
|
||||||
echo " verification build failed after hash update" >&2
|
# exercises npmConfigHook the same way CI does, so a "fixed" hash
|
||||||
|
# that would still break the downstream build gets caught here
|
||||||
|
# rather than after we push the fix.
|
||||||
|
if ! nix build ".#$ATTR" --no-link --print-build-logs; then
|
||||||
|
echo " verification build of .#$ATTR failed after hash update" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
FIXED=1
|
FIXED=1
|
||||||
echo " fixed"
|
echo " fixed and verified"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ let
|
|||||||
src = ../ui-tui;
|
src = ../ui-tui;
|
||||||
npmDeps = pkgs.fetchNpmDeps {
|
npmDeps = pkgs.fetchNpmDeps {
|
||||||
inherit src;
|
inherit src;
|
||||||
hash = "sha256-a/HGI9OgVcTnZrMXA7xFMGnFoVxyHe95fulVz+WNYB0=";
|
hash = "sha256-MLcLhjTF6dgdvNBtJWzo8Nh19eNh/ZitD2b07nm61Tc=";
|
||||||
};
|
};
|
||||||
|
|
||||||
npm = hermesNpmLib.mkNpmPassthru { folder = "ui-tui"; attr = "tui"; pname = "hermes-tui"; };
|
npm = hermesNpmLib.mkNpmPassthru { folder = "ui-tui"; attr = "tui"; pname = "hermes-tui"; };
|
||||||
|
|||||||
Reference in New Issue
Block a user