fix(ci): fix Windows image digest extraction and Docker daemon readiness#276
Conversation
Apply five targeted fixes across all Windows workflow files: 1. Add "Wait for Docker daemon" step before any docker commands to handle cases where the Docker service is not yet ready on Windows runners, preventing spurious login/pull failures. 2. Fix digest extraction to use RepoDigests instead of the incorrect Config.Image path, which returns the base image hash rather than the actual pushed image digest. 3. Handle "image already exists" gracefully in the retry workflow by extracting the existing digest and reporting success instead of failing the entire workflow. 4. Scope "Report failure" conditions to reference specific build step outcomes rather than using broad failure()/cancelled() checks that would misfire when unrelated steps (like report-publication) fail. 5. Fix missing $ prefixes in hub workflow's workflow_dispatch branch where github.event.inputs expressions were not being evaluated. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughFive Windows GitHub workflows are updated with a new "Wait for Docker daemon" step that polls the Docker service with retries and starts it if stopped. Additionally, image digest extraction is modified to use RepoDigests instead of Config.Image, parsing the digest portion after the '@' separator. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Readiness & Safety AssessmentWhat's the actual fix vs defense-in-depth?Fix 2 (digest extraction) is the root cause fix. The field The other fixes are defense-in-depth:
Can this be tested before merge?No integration testing is possible before merge. The Windows build workflows only execute via However, all fixes are validatable by code review:
Safety
RecommendationMerge-and-observe. The last 30+ Windows workflow runs on |
I don't think this has ever failed. Should we be fixing a non-issue?
Nice catch if that is indeed the case. Have you verified that it doesn't report the right hash?
This is currently failing by design. Because if an image already exists, the integrity of versioning backend isn't correct. If the actual problem gets fixed, then this should not be a problem. This fix is then only hiding the problem allowing silent degredation of the system.
The system needs to know not just the build failure, but also if it failed to upload
Was it in fact broken? I remember rescheduling builds from the versioning page working fine. Note that while this might be a good fix, it doesn't fix the actual problem the pipeline is suffering from. I would suggest to first focus on solving the real issue at hand, get to a state where we can guarantee it can build all target platforms for all versions on all platforms without getting stuck halfway though. I haven't checked the latest issue, but usually the issue is one of two things:
or
Note, in the first four years, point 2 has never ever happened, because of the integrity of the system. With the introduction of some workflows and/or a non integer change to versioning backend, this record was broken. See also my previous PR with attempted (partial) fix and comments on Discord about how to reason about it. |
Remove fixes 3-5 to keep the PR focused on the two confirmed root causes: - Fix 1: Docker daemon readiness check before build (all 5 workflows) - Fix 2: Digest extraction via RepoDigests[0] instead of Config.Image (all 5 workflows) Reverted changes: - Fix 3: retry workflow "already exists" skip-build logic - Fix 4: Report failure condition scoping (restored failure()||cancelled()) - Fix 5: Missing $ prefixes in hub workflow (debatable whether broken) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Thanks for the thorough review. You're right — I was over-scoping this. I've trimmed the PR down to only the two root cause fixes and dropped fixes 3/4/5 that were undermining the system's integrity. Also closed the versioning-backend PR (#80) — the scheduling system isn't the problem. Here's what remains and the evidence: Digest extraction — the actual root causeVerified in run logs. The GitHub runner image update around Feb 17 ( Before Feb 18 (Docker 27.5.1, run "Config": {
"Hostname": "",
"Domainname": "",
"Image": "sha256:afe51f0a3f631c02630454c8a2ea6d3aacad16ecf7641cbe3dcab157993f2299",
...
}
After Feb 18 (containerd image store, run "Config": {
"Cmd": ["c:\windows\system32\cmd.exe"],
"Env": [...],
"Shell": ["powershell.exe", "-Command"]
}
This error appears in every failing Windows run since Feb 18 across all target platforms. The fix replaces Docker daemon readiness — secondary but realThe
This correlates with actions/runner-images#13729, a confirmed regression in Dropped from PR
Also closed versioning-backend#80 — the self-healing system works correctly once the builds stop failing. |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/new-windows-base-image-requested.yml:
- Around line 157-158: Guard access to RepoDigests before indexing: check that
$ImageDetails[0].RepoDigests exists and has at least one element, then extract
$RepoDigest and split to get $Digest and validate $Digest is non-empty; if the
array is missing or the parsed digest is empty, log an error and exit non-zero
(or fail the job) instead of continuing. Update the same pattern where
$RepoDigest, $ImageDetails, $Digest, or RepoDigests are used in the listed
workflows.
In @.github/workflows/new-windows-legacy-editor-image-requested.yml:
- Around line 230-231: The code assumes $ImageDetails[0].RepoDigests[0] exists
and blindly sets $RepoDigest and $Digest; add defensive checks to validate
$ImageDetails and $ImageDetails[0].RepoDigests are non-null and non-empty before
accessing index 0, and if validation fails write a clear error and set the job
step to failure (e.g., throw/exit with non-zero) so we don't emit digest=$null;
update the logic around $RepoDigest and $Digest to only parse ($RepoDigest
-split '@')[1] after those checks and fail the step with a descriptive message
when digest extraction cannot be performed.
In @.github/workflows/new-windows-post-2019-2-editor-image-requested.yml:
- Around line 237-238: Add explicit guards before reading
$ImageDetails[0].RepoDigests and before using the -split '@' result: check that
$ImageDetails is non-null and has at least one element, that
$ImageDetails[0].RepoDigests exists and has Length -gt 0, and that ($RepoDigest
-split '@') returns at least two elements before accessing index [1] to assign
$Digest; apply the same checks to the identical pattern in the other 4 workflow
files.
In @.github/workflows/retry-windows-editor-image-requested.yml:
- Around line 169-170: Validate that ImageDetails[0].RepoDigests exists and has
at least one element before assigning $RepoDigest and ensure the split on '@'
yields two parts before assigning $Digest; specifically, guard access to
$ImageDetails and its RepoDigests array (check for $null and .Count -gt 0), only
set $RepoDigest = $ImageDetails[0].RepoDigests[0] when present, then perform the
($RepoDigest -split '@') and verify the resulting array has a second element
before assigning $Digest, otherwise handle the missing value (log/throw/set a
clear default) so downstream reporting does not get a silent empty value.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d1990e27-8a0b-4bf6-abef-5e0fa922db4e
📒 Files selected for processing (5)
.github/workflows/new-windows-base-image-requested.yml.github/workflows/new-windows-hub-image-requested.yml.github/workflows/new-windows-legacy-editor-image-requested.yml.github/workflows/new-windows-post-2019-2-editor-image-requested.yml.github/workflows/retry-windows-editor-image-requested.yml
| $RepoDigest = $ImageDetails[0].RepoDigests[0] | ||
| $Digest = ($RepoDigest -split '@')[1] |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify all affected workflows use unguarded RepoDigests indexing.
rg -n -C2 '\$RepoDigest = \$ImageDetails\[0\]\.RepoDigests\[0\]|\$Digest = \(\$RepoDigest -split '\''@'\''\)\[1\]' \
.github/workflows/new-windows-base-image-requested.yml \
.github/workflows/new-windows-hub-image-requested.yml \
.github/workflows/new-windows-legacy-editor-image-requested.yml \
.github/workflows/new-windows-post-2019-2-editor-image-requested.yml \
.github/workflows/retry-windows-editor-image-requested.ymlRepository: game-ci/docker
Length of output: 4167
Guard RepoDigests before indexing across all image build workflows.
Lines 157–158 in .github/workflows/new-windows-base-image-requested.yml (and the same pattern in 4 other workflows) assume RepoDigests[0] exists without validation. If the array is empty or missing, the digest extraction silently fails, producing an empty value while the workflow continues to report success.
This pattern appears in:
.github/workflows/new-windows-base-image-requested.yml(157–158).github/workflows/new-windows-hub-image-requested.yml(163–164).github/workflows/new-windows-legacy-editor-image-requested.yml(230–231).github/workflows/new-windows-post-2019-2-editor-image-requested.yml(237–238).github/workflows/retry-windows-editor-image-requested.yml(169–170)
Add presence checks before accessing RepoDigests[0] and validate the parsed digest is non-empty:
Proposed hardening
$MetaData = docker inspect unityci/base:windows-${{ steps.buildParameters.outputs.repoVersionFull }}
$ImageDetails = $MetaData | ConvertFrom-Json
- $RepoDigest = $ImageDetails[0].RepoDigests[0]
- $Digest = ($RepoDigest -split '@')[1]
+ if (-not $ImageDetails -or -not $ImageDetails[0].RepoDigests -or $ImageDetails[0].RepoDigests.Count -eq 0) {
+ Write-Error "RepoDigest not found after push."
+ exit 1
+ }
+ $RepoDigest = $ImageDetails[0].RepoDigests[0]
+ $Digest = ($RepoDigest -split '@')[1]
+ if (-not $Digest) {
+ Write-Error "Failed to parse digest from RepoDigest: $RepoDigest"
+ exit 1
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| $RepoDigest = $ImageDetails[0].RepoDigests[0] | |
| $Digest = ($RepoDigest -split '@')[1] | |
| if (-not $ImageDetails -or -not $ImageDetails[0].RepoDigests -or $ImageDetails[0].RepoDigests.Count -eq 0) { | |
| Write-Error "RepoDigest not found after push." | |
| exit 1 | |
| } | |
| $RepoDigest = $ImageDetails[0].RepoDigests[0] | |
| $Digest = ($RepoDigest -split '@')[1] | |
| if (-not $Digest) { | |
| Write-Error "Failed to parse digest from RepoDigest: $RepoDigest" | |
| exit 1 | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/new-windows-base-image-requested.yml around lines 157 -
158, Guard access to RepoDigests before indexing: check that
$ImageDetails[0].RepoDigests exists and has at least one element, then extract
$RepoDigest and split to get $Digest and validate $Digest is non-empty; if the
array is missing or the parsed digest is empty, log an error and exit non-zero
(or fail the job) instead of continuing. Update the same pattern where
$RepoDigest, $ImageDetails, $Digest, or RepoDigests are used in the listed
workflows.
| $RepoDigest = $ImageDetails[0].RepoDigests[0] | ||
| $Digest = ($RepoDigest -split '@')[1] |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
head -n 250 .github/workflows/new-windows-legacy-editor-image-requested.yml | tail -n 30Repository: game-ci/docker
Length of output: 1444
🏁 Script executed:
sed -n '200,240p' .github/workflows/new-windows-legacy-editor-image-requested.ymlRepository: game-ci/docker
Length of output: 3005
🏁 Script executed:
sed -n '1,50p' .github/workflows/new-windows-legacy-editor-image-requested.ymlRepository: game-ci/docker
Length of output: 1494
🏁 Script executed:
rg -A3 "RepoDigest" .github/workflows/Repository: game-ci/docker
Length of output: 2659
🏁 Script executed:
rg "ConvertFrom-Json" .github/workflows/ -B2 -A5Repository: game-ci/docker
Length of output: 4701
Add null/empty checks before accessing and parsing RepoDigests.
Lines 230-231 assume digest data exists without validation. If RepoDigests is empty or the property is missing, the array access returns $null, and splitting $null produces unexpected results. This causes $Digest to be $null, silently writing invalid metadata (digest=$null) to GITHUB_OUTPUT and corrupting downstream reporting. The same vulnerable pattern exists across five workflow files:
- new-windows-legacy-editor-image-requested.yml
- new-windows-post-2019-2-editor-image-requested.yml
- retry-windows-editor-image-requested.yml
- new-windows-hub-image-requested.yml
- new-windows-base-image-requested.yml
Add validation before parsing: check that $ImageDetails and $ImageDetails[0].RepoDigests are non-empty, and fail the step if digest extraction fails.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/new-windows-legacy-editor-image-requested.yml around lines
230 - 231, The code assumes $ImageDetails[0].RepoDigests[0] exists and blindly
sets $RepoDigest and $Digest; add defensive checks to validate $ImageDetails and
$ImageDetails[0].RepoDigests are non-null and non-empty before accessing index
0, and if validation fails write a clear error and set the job step to failure
(e.g., throw/exit with non-zero) so we don't emit digest=$null; update the logic
around $RepoDigest and $Digest to only parse ($RepoDigest -split '@')[1] after
those checks and fail the step with a descriptive message when digest extraction
cannot be performed.
| $RepoDigest = $ImageDetails[0].RepoDigests[0] | ||
| $Digest = ($RepoDigest -split '@')[1] |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n .github/workflows/new-windows-post-2019-2-editor-image-requested.yml | sed -n '230,250p'Repository: game-ci/docker
Length of output: 1083
🏁 Script executed:
head -n 235 .github/workflows/new-windows-post-2019-2-editor-image-requested.yml | tail -n 100Repository: game-ci/docker
Length of output: 6618
🏁 Script executed:
rg "RepoDigests\|docker inspect" -B 3 -A 3 --type yamlRepository: game-ci/docker
Length of output: 206
🏁 Script executed:
rg "RepoDigests" -B 3 -A 3Repository: game-ci/docker
Length of output: 40
🏁 Script executed:
find .github/workflows -name "*.yml" -exec grep -l "docker inspect" {} \;Repository: game-ci/docker
Length of output: 358
🏁 Script executed:
grep -n "RepoDigest\|-split '@'" .github/workflows/new-windows-base-image-requested.yml .github/workflows/new-windows-hub-image-requested.yml .github/workflows/new-windows-legacy-editor-image-requested.yml .github/workflows/retry-windows-editor-image-requested.ymlRepository: game-ci/docker
Length of output: 969
🏁 Script executed:
grep -n "if.*RepoDigests\|if.*-split\|error\|try\|catch" .github/workflows/new-windows-base-image-requested.yml | head -20Repository: game-ci/docker
Length of output: 235
Add explicit guards before accessing RepoDigests array and parsing digest.
Lines 237-238 lack null/length checks. Add validation that $ImageDetails[0].RepoDigests is not empty and that the split operation produces at least 2 elements before accessing index [1]. This pattern appears identically in 5 workflow files and should be hardened across all of them.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/new-windows-post-2019-2-editor-image-requested.yml around
lines 237 - 238, Add explicit guards before reading $ImageDetails[0].RepoDigests
and before using the -split '@' result: check that $ImageDetails is non-null and
has at least one element, that $ImageDetails[0].RepoDigests exists and has
Length -gt 0, and that ($RepoDigest -split '@') returns at least two elements
before accessing index [1] to assign $Digest; apply the same checks to the
identical pattern in the other 4 workflow files.
| $RepoDigest = $ImageDetails[0].RepoDigests[0] | ||
| $Digest = ($RepoDigest -split '@')[1] |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd -t f "retry-windows-editor-image-requested.yml" --exec cat -n {}Repository: game-ci/docker
Length of output: 40
🏁 Script executed:
git ls-files | grep -i "retry-windows-editor-image-requested"Repository: game-ci/docker
Length of output: 116
🏁 Script executed:
cat -n .github/workflows/retry-windows-editor-image-requested.yml | sed -n '150,190p'Repository: game-ci/docker
Length of output: 2828
Add validation before accessing RepoDigests array.
Lines 169–170 access nested array elements without guards. If docker inspect returns incomplete data or RepoDigests is empty, $RepoDigest becomes $null, causing $Digest to be $null and silently outputting an empty value downstream in the reporting step. Add checks to verify RepoDigests is non-empty and the split produces the expected parts before proceeding.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/retry-windows-editor-image-requested.yml around lines 169
- 170, Validate that ImageDetails[0].RepoDigests exists and has at least one
element before assigning $RepoDigest and ensure the split on '@' yields two
parts before assigning $Digest; specifically, guard access to $ImageDetails and
its RepoDigests array (check for $null and .Count -gt 0), only set $RepoDigest =
$ImageDetails[0].RepoDigests[0] when present, then perform the ($RepoDigest
-split '@') and verify the resulting array has a second element before assigning
$Digest, otherwise handle the missing value (log/throw/set a clear default) so
downstream reporting does not get a silent empty value.
webbertakken
left a comment
There was a problem hiding this comment.
Thanks for the revision and the super clean explanation!
LGTM!

Summary
Fixes the Windows Docker image build pipeline, which has been failing on every run since ~Feb 18, 2026.
Root cause: The GitHub Actions
windows-2022runner image updated Docker to use the containerd image store, which returns a stripped-downdocker inspectformat. TheConfig.Imagefield used for digest extraction is completely absent in this format, causing every Windows build to fail at the "Report publication" step withError: Input required and not supplied: digest.Changes
1. Fix digest extraction (all 5 Windows workflows)
Replaces:
With:
RepoDigestsis present in both old and new Docker inspect formats and contains the actual registry digest after push.Evidence from run logs:
21906028620):Config.Imagepopulated → builds succeeded22401227174):Config.Imageabsent → every build fails with empty digest2. Add Docker daemon readiness check (all 5 Windows workflows)
Adds a polling step before
docker loginthat waits for the Docker service to be running and responsive. Works around actions/runner-images#13729, a confirmed regression where the Docker Engine service sometimes fails to start onwindows-2022runners.The step is a no-op when Docker is already running (exits immediately). Can be removed once the upstream fix is deployed.
Files changed
.github/workflows/new-windows-base-image-requested.yml.github/workflows/new-windows-hub-image-requested.yml.github/workflows/new-windows-legacy-editor-image-requested.yml.github/workflows/new-windows-post-2019-2-editor-image-requested.yml.github/workflows/retry-windows-editor-image-requested.ymlTest plan
RepoDigests[0]produces a validsha256:...digest on a Windows build🤖 Generated with Claude Code