Skip to content

Commit 06c543e

Browse files
fix(nix): resolve hash race condition in parallel matrix jobs (anomalyco#8995)
1 parent 759ce8f commit 06c543e

3 files changed

Lines changed: 173 additions & 152 deletions

File tree

.github/workflows/nix-desktop.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ on:
99
- "nix/**"
1010
- "packages/app/**"
1111
- "packages/desktop/**"
12+
- ".github/workflows/nix-desktop.yml"
1213
pull_request:
1314
paths:
1415
- "flake.nix"
1516
- "flake.lock"
1617
- "nix/**"
1718
- "packages/app/**"
1819
- "packages/desktop/**"
20+
- ".github/workflows/nix-desktop.yml"
1921
workflow_dispatch:
2022

2123
jobs:
@@ -26,7 +28,7 @@ jobs:
2628
os:
2729
- blacksmith-4vcpu-ubuntu-2404
2830
- blacksmith-4vcpu-ubuntu-2404-arm
29-
- macos-15
31+
- macos-15-intel
3032
- macos-latest
3133
runs-on: ${{ matrix.os }}
3234
timeout-minutes: 60

.github/workflows/update-nix-hashes.yml

Lines changed: 170 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ on:
1010
- "bun.lock"
1111
- "package.json"
1212
- "packages/*/package.json"
13+
- ".github/workflows/update-nix-hashes.yml"
1314
pull_request:
1415
paths:
1516
- "bun.lock"
1617
- "package.json"
1718
- "packages/*/package.json"
19+
- ".github/workflows/update-nix-hashes.yml"
1820

1921
jobs:
2022
update-flake:
@@ -25,7 +27,7 @@ jobs:
2527

2628
steps:
2729
- name: Checkout repository
28-
uses: actions/checkout@v4
30+
uses: actions/checkout@v6
2931
with:
3032
token: ${{ secrets.GITHUB_TOKEN }}
3133
fetch-depth: 0
@@ -43,17 +45,17 @@ jobs:
4345
- name: Update ${{ env.TITLE }}
4446
run: |
4547
set -euo pipefail
46-
echo "📦 Updating $TITLE..."
48+
echo "Updating $TITLE..."
4749
nix flake update
48-
echo "$TITLE updated successfully"
50+
echo "$TITLE updated successfully"
4951
5052
- name: Commit ${{ env.TITLE }} changes
5153
env:
5254
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
5355
run: |
5456
set -euo pipefail
5557
56-
echo "🔍 Checking for changes in tracked files..."
58+
echo "Checking for changes in tracked files..."
5759
5860
summarize() {
5961
local status="$1"
@@ -71,29 +73,29 @@ jobs:
7173
FILES=(flake.lock flake.nix)
7274
STATUS="$(git status --short -- "${FILES[@]}" || true)"
7375
if [ -z "$STATUS" ]; then
74-
echo "No changes detected."
76+
echo "No changes detected."
7577
summarize "no changes"
7678
exit 0
7779
fi
7880
79-
echo "📝 Changes detected:"
81+
echo "Changes detected:"
8082
echo "$STATUS"
81-
echo "🔗 Staging files..."
83+
echo "Staging files..."
8284
git add "${FILES[@]}"
83-
echo "💾 Committing changes..."
85+
echo "Committing changes..."
8486
git commit -m "Update $TITLE"
85-
echo "Changes committed"
87+
echo "Changes committed"
8688
8789
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
88-
echo "🌳 Pulling latest from branch: $BRANCH"
89-
git pull --rebase origin "$BRANCH"
90-
echo "🚀 Pushing changes to branch: $BRANCH"
90+
echo "Pulling latest from branch: $BRANCH"
91+
git pull --rebase --autostash origin "$BRANCH"
92+
echo "Pushing changes to branch: $BRANCH"
9193
git push origin HEAD:"$BRANCH"
92-
echo "Changes pushed successfully"
94+
echo "Changes pushed successfully"
9395
9496
summarize "committed $(git rev-parse --short HEAD)"
9597
96-
update-node-modules-hash:
98+
compute-node-modules-hash:
9799
needs: update-flake
98100
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
99101
strategy:
@@ -111,11 +113,10 @@ jobs:
111113
runs-on: ${{ matrix.host }}
112114
env:
113115
SYSTEM: ${{ matrix.system }}
114-
TITLE: node_modules hash (${{ matrix.system }})
115116

116117
steps:
117118
- name: Checkout repository
118-
uses: actions/checkout@v4
119+
uses: actions/checkout@v6
119120
with:
120121
token: ${{ secrets.GITHUB_TOKEN }}
121122
fetch-depth: 0
@@ -125,6 +126,104 @@ jobs:
125126
- name: Setup Nix
126127
uses: nixbuild/nix-quick-install-action@v34
127128

129+
- name: Compute node_modules hash
130+
run: |
131+
set -euo pipefail
132+
133+
DUMMY="sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
134+
HASH_FILE="nix/hashes.json"
135+
OUTPUT_FILE="hash-${SYSTEM}.txt"
136+
137+
export NIX_KEEP_OUTPUTS=1
138+
export NIX_KEEP_DERIVATIONS=1
139+
140+
BUILD_LOG=$(mktemp)
141+
TMP_JSON=$(mktemp)
142+
trap 'rm -f "$BUILD_LOG" "$TMP_JSON"' EXIT
143+
144+
if [ ! -f "$HASH_FILE" ]; then
145+
mkdir -p "$(dirname "$HASH_FILE")"
146+
echo '{"nodeModules":{}}' > "$HASH_FILE"
147+
fi
148+
149+
# Set dummy hash to force nix to rebuild and reveal correct hash
150+
jq --arg system "$SYSTEM" --arg value "$DUMMY" \
151+
'.nodeModules = (.nodeModules // {}) | .nodeModules[$system] = $value' "$HASH_FILE" > "$TMP_JSON"
152+
mv "$TMP_JSON" "$HASH_FILE"
153+
154+
MODULES_ATTR=".#packages.${SYSTEM}.default.node_modules"
155+
DRV_PATH="$(nix eval --raw "${MODULES_ATTR}.drvPath")"
156+
157+
echo "Building node_modules for ${SYSTEM} to discover correct hash..."
158+
echo "Attempting to realize derivation: ${DRV_PATH}"
159+
REALISE_OUT=$(nix-store --realise "$DRV_PATH" --keep-failed 2>&1 | tee "$BUILD_LOG" || true)
160+
161+
BUILD_PATH=$(echo "$REALISE_OUT" | grep "^/nix/store/" | head -n1 || true)
162+
CORRECT_HASH=""
163+
164+
if [ -n "$BUILD_PATH" ] && [ -d "$BUILD_PATH" ]; then
165+
echo "Realized node_modules output: $BUILD_PATH"
166+
CORRECT_HASH=$(nix hash path --sri "$BUILD_PATH" 2>/dev/null || true)
167+
fi
168+
169+
# Try to extract hash from build log
170+
if [ -z "$CORRECT_HASH" ]; then
171+
CORRECT_HASH="$(grep -E 'got:\s+sha256-[A-Za-z0-9+/=]+' "$BUILD_LOG" | awk '{print $2}' | head -n1 || true)"
172+
fi
173+
174+
if [ -z "$CORRECT_HASH" ]; then
175+
CORRECT_HASH="$(grep -A2 'hash mismatch' "$BUILD_LOG" | grep 'got:' | awk '{print $2}' | sed 's/sha256:/sha256-/' || true)"
176+
fi
177+
178+
# Try to hash from kept failed build directory
179+
if [ -z "$CORRECT_HASH" ]; then
180+
KEPT_DIR=$(grep -oE "build directory.*'[^']+'" "$BUILD_LOG" | grep -oE "'/[^']+'" | tr -d "'" | head -n1 || true)
181+
if [ -z "$KEPT_DIR" ]; then
182+
KEPT_DIR=$(grep -oE '/nix/var/nix/builds/[^ ]+' "$BUILD_LOG" | head -n1 || true)
183+
fi
184+
185+
if [ -n "$KEPT_DIR" ] && [ -d "$KEPT_DIR" ]; then
186+
HASH_PATH="$KEPT_DIR"
187+
[ -d "$KEPT_DIR/build" ] && HASH_PATH="$KEPT_DIR/build"
188+
189+
if [ -d "$HASH_PATH/node_modules" ]; then
190+
CORRECT_HASH=$(nix hash path --sri "$HASH_PATH" 2>/dev/null || true)
191+
fi
192+
fi
193+
fi
194+
195+
if [ -z "$CORRECT_HASH" ]; then
196+
echo "Failed to determine correct node_modules hash for ${SYSTEM}."
197+
cat "$BUILD_LOG"
198+
exit 1
199+
fi
200+
201+
echo "$CORRECT_HASH" > "$OUTPUT_FILE"
202+
echo "Hash for ${SYSTEM}: $CORRECT_HASH"
203+
204+
- name: Upload hash artifact
205+
uses: actions/upload-artifact@v6
206+
with:
207+
name: hash-${{ matrix.system }}
208+
path: hash-${{ matrix.system }}.txt
209+
retention-days: 1
210+
211+
commit-node-modules-hashes:
212+
needs: compute-node-modules-hash
213+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
214+
runs-on: blacksmith-4vcpu-ubuntu-2404
215+
env:
216+
TITLE: node_modules hashes
217+
218+
steps:
219+
- name: Checkout repository
220+
uses: actions/checkout@v6
221+
with:
222+
token: ${{ secrets.GITHUB_TOKEN }}
223+
fetch-depth: 0
224+
ref: ${{ github.head_ref || github.ref_name }}
225+
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
226+
128227
- name: Configure git
129228
run: |
130229
git config --global user.email "action@github.com"
@@ -135,22 +234,66 @@ jobs:
135234
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
136235
run: |
137236
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
138-
git pull origin "$BRANCH"
237+
git pull --rebase --autostash origin "$BRANCH"
139238
140-
- name: Update ${{ env.TITLE }}
239+
- name: Download all hash artifacts
240+
uses: actions/download-artifact@v7
241+
with:
242+
pattern: hash-*
243+
merge-multiple: true
244+
245+
- name: Merge hashes into hashes.json
141246
run: |
142247
set -euo pipefail
143-
echo "🔄 Updating $TITLE..."
144-
nix/scripts/update-hashes.sh
145-
echo "✅ $TITLE updated successfully"
248+
249+
HASH_FILE="nix/hashes.json"
250+
251+
if [ ! -f "$HASH_FILE" ]; then
252+
mkdir -p "$(dirname "$HASH_FILE")"
253+
echo '{"nodeModules":{}}' > "$HASH_FILE"
254+
fi
255+
256+
echo "Merging hashes into ${HASH_FILE}..."
257+
258+
shopt -s nullglob
259+
files=(hash-*.txt)
260+
if [ ${#files[@]} -eq 0 ]; then
261+
echo "No hash files found, nothing to update"
262+
exit 0
263+
fi
264+
265+
EXPECTED_SYSTEMS="x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin"
266+
for sys in $EXPECTED_SYSTEMS; do
267+
if [ ! -f "hash-${sys}.txt" ]; then
268+
echo "WARNING: Missing hash file for $sys"
269+
fi
270+
done
271+
272+
for f in "${files[@]}"; do
273+
system="${f#hash-}"
274+
system="${system%.txt}"
275+
hash=$(cat "$f")
276+
if [ -z "$hash" ]; then
277+
echo "WARNING: Empty hash for $system, skipping"
278+
continue
279+
fi
280+
echo " $system: $hash"
281+
jq --arg sys "$system" --arg h "$hash" \
282+
'.nodeModules = (.nodeModules // {}) | .nodeModules[$sys] = $h' "$HASH_FILE" > "${HASH_FILE}.tmp"
283+
mv "${HASH_FILE}.tmp" "$HASH_FILE"
284+
done
285+
286+
echo "All hashes merged:"
287+
cat "$HASH_FILE"
146288
147289
- name: Commit ${{ env.TITLE }} changes
148290
env:
149291
TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
150292
run: |
151293
set -euo pipefail
152294
153-
echo "🔍 Checking for changes in tracked files..."
295+
HASH_FILE="nix/hashes.json"
296+
echo "Checking for changes..."
154297
155298
summarize() {
156299
local status="$1"
@@ -166,27 +309,22 @@ jobs:
166309
echo "" >> "$GITHUB_STEP_SUMMARY"
167310
}
168311
169-
FILES=(nix/hashes.json)
312+
FILES=("$HASH_FILE")
170313
STATUS="$(git status --short -- "${FILES[@]}" || true)"
171314
if [ -z "$STATUS" ]; then
172-
echo "No changes detected."
315+
echo "No changes detected."
173316
summarize "no changes"
174317
exit 0
175318
fi
176319
177-
echo "📝 Changes detected:"
320+
echo "Changes detected:"
178321
echo "$STATUS"
179-
echo "🔗 Staging files..."
180322
git add "${FILES[@]}"
181-
echo "💾 Committing changes..."
182323
git commit -m "Update $TITLE"
183-
echo "✅ Changes committed"
184324
185325
BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}"
186-
echo "🌳 Pulling latest from branch: $BRANCH"
187-
git pull --rebase origin "$BRANCH"
188-
echo "🚀 Pushing changes to branch: $BRANCH"
326+
git pull --rebase --autostash origin "$BRANCH"
189327
git push origin HEAD:"$BRANCH"
190-
echo "Changes pushed successfully"
328+
echo "Changes pushed successfully"
191329
192330
summarize "committed $(git rev-parse --short HEAD)"

0 commit comments

Comments
 (0)