fix: P0 CI failures — empty-SHA cache entries and stale action pins#48291
Conversation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Restores CI reliability for #48288 by preventing invalid action-cache entries and refreshing stale action pins.
Changes:
- Prevents metadata setters from creating SHA-less cache entries.
- Corrects the safe-output integration assertion.
- Updates moved action SHAs and regenerated workflow locks.
Show a summary per file
| File | Description |
|---|---|
pkg/workflow/data/action_pins.json |
Refreshes embedded action SHAs. |
pkg/workflow/action_cache.go |
Guards cache metadata updates. |
pkg/workflow/action_cache_test.go |
Tests missing-entry input behavior. |
pkg/cli/compile_integration_test.go |
Corrects the generated script assertion. |
pkg/actionpins/data/action_pins.json |
Refreshes action-pin source data. |
.github/workflows/smoke-codex.lock.yml |
Regenerates the label-action pin. |
.github/workflows/hippo-embed.lock.yml |
Regenerates the PMG pin. |
.github/workflows/dataflow-pr-discussion-dataset.lock.yml |
Regenerates the PMG pin. |
.github/aw/actions-lock.json |
Updates repository action locks. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comments suppressed due to low confidence (1)
pkg/workflow/action_cache.go:574
- For a new target version, this condition is always true—the target is not pinned yet—which disables the cooldown cache in
update_actions.go:190-198. While that release remains in cooldown, everyupdateinvocation must query GitHub again, increasing API usage and rate-limit risk. Keep cooldown metadata separately from pin entries (or otherwise persist it without exposing a SHA-less action entry) so rejecting invalid pins does not remove the cache.
if !exists || entry.SHA == "" {
actionCacheLog.Printf("No existing cache entry with SHA for key=%s, skipping release date update", key)
return
- Files reviewed: 9/9 changed files
- Comments generated: 1
- Review effort level: Medium
| if !exists || entry.SHA == "" { | ||
| actionCacheLog.Printf("No existing cache entry with SHA for key=%s, skipping inputs update", key) | ||
| return |
There was a problem hiding this comment.
Addressed in c007ddf: when safe-output metadata is fetched, we now seed the action-cache entry with the resolved full SHA (when available) before storing inputs/description, so metadata persists for embedded-pin resolutions as well.
|
✅ Design Decision Gate 🏗️ completed the design decision gate check. No ADR enforcement needed: PR #48291 does not have the 'implementation' label and has only 33 new lines of code in business logic directories (threshold: 100). |
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅ |
|
✅ Test Quality Sentinel completed test quality analysis. No test files were added or modified in this PR. Only minor assertion updates in existing tests to accommodate action name changes (safe_output_handler_manager.cjs → process_safe_outputs.cjs) and cache behavior fixes. Test Quality Sentinel skipped. |
|
✅ PR Code Quality Reviewer completed the code quality review. |
There was a problem hiding this comment.
Review: P0 CI failures fix
The changes are correct and well-scoped.
Root cause fix (action_cache.go): Making SetReleasedAt, SetInputs, and SetActionDescription no-ops when no entry with a non-empty SHA exists is the right approach. It prevents phantom cache entries from polluting actions-lock.json and causing validateUpdateSHAEntries failures downstream.
Test updates (action_cache_test.go): Properly inverted to validate the new no-op semantics.
SHA fixes and lock file recompilation: The stale SHA corrections for actions-ecosystem/action-add-labels@v1.1.3 and safedep/pmg@v1 are appropriate manual interventions for force-pushed/moving tags.
No blocking issues found. LGTM.
🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 14.9 AIC · ⌖ 4.54 AIC · ⊞ 5K
🤖 PR Triage
Score breakdown: Impact 44/50 · Urgency 28/30 · Quality 16/20 Rationale: P0 CI failure affecting
|
There was a problem hiding this comment.
Skills-Based Review 🧠
Applied /diagnosing-bugs — the fix is correct and the test updates match. One follow-up observation posted inline.
📋 Key Themes & Highlights
Positive Highlights
- ✅ Root cause correctly identified and fixed:
SetReleasedAt/SetInputs/SetActionDescriptionnow guard against creating empty-SHA shell entries - ✅ The guard condition (
!exists || entry.SHA == "") is applied uniformly across all three setters — good consistency - ✅ Test updated to match the new no-op semantics; old test was asserting the wrong (buggy) behaviour
- ✅ Compile integration test corrected to match the actual emitted filename (
process_safe_outputs.cjs) - ✅ Stale SHA entries updated and lock files recompiled — straightforward operational fix
Observation
One untested scenario: if Set is called to clear/reset the SHA on an entry that already has populated Inputs, ReleasedAt, or ActionDescription, those fields will survive the SHA-wipe and the entry will re-appear as a non-empty-SHA entry on the next Set call (since Set overwrites). This is a narrow but real gap — see inline comment for a suggested regression test.
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 24.1 AIC · ⌖ 4.75 AIC · ⊞ 6.7K
Comment /matt to run again
| if !exists || entry.SHA == "" { | ||
| actionCacheLog.Printf("No existing cache entry with SHA for key=%s, skipping inputs update", key) | ||
| return | ||
| } |
There was a problem hiding this comment.
[/diagnosing-bugs] The guard !exists || entry.SHA == "" is applied consistently across SetInputs, SetReleasedAt, and SetActionDescription — good. One edge case to consider: if Set is later called to wipe the SHA on an entry that already has inputs/description stored, those metadata fields will survive the SHA-wipe and could re-introduce a stale/invalid cache entry on next save.
💡 Suggested regression test sketch
// Confirm metadata is cleared when SHA is reset to empty
cache.Set("owner/repo", "v1", "abc123", time.Now())
cache.SetInputs("owner/repo", "v1", inputs)
cache.Set("owner/repo", "v1", "", time.Now()) // SHA wiped
_, ok := cache.GetInputs("owner/repo", "v1")
if ok {
t.Error("expected inputs cleared after SHA wipe")
}Without this, a SHA-reset followed by a re-fetch could leave orphaned metadata that bypasses the empty-SHA guard on the next Set call.
@copilot please address this.
There was a problem hiding this comment.
Handled in c007ddf. Set already rejects empty SHA, so the specific “SHA wipe to empty” path is not reachable; I also added explicit empty-SHA-entry no-op regression coverage for SetReleasedAt, SetInputs, and SetActionDescription to protect the guard behavior.
|
@copilot run pr-finisher skill |
There was a problem hiding this comment.
REQUEST_CHANGES — one high-impact correctness regression, one missing test for the primary bug scenario
The empty-SHA guard logic correctly fixes the P0, but the implementation is too broad in one case and undertested in the critical path.
Blocking findings
1. Cooldown release-date caching is now broken (high) — SetReleasedAt at update_actions.go:197 is called with latestVersion, which has no cache entry at that point in the code. The !exists || entry.SHA == "" guard fires every time, discarding the date. Every gh aw update run will re-fetch release dates from the GitHub API for any action in cooldown rather than using the cache. The original bug required preventing disk persistence of empty-SHA entries; the fix should have targeted Save() or used a transient metadata map rather than blocking in-memory writes.
2. Empty-SHA entry branch is not tested (medium) — The new test in action_cache_test.go only covers the !exists path. The entry.SHA == "" path — the exact scenario that caused the P0 — has no regression test. A future refactor could accidentally remove that guard and CI would not catch it.
3. SetInputs accepts nil and silently clears cached inputs (medium) — No nil guard on the inputs parameter. An existing entry with a valid SHA will have its inputs wiped if a caller passes nil. The current callsite guards against this, but the method doesn't enforce its own contract.
🔎 Code quality review by PR Code Quality Reviewer · sonnet46 · 67.1 AIC · ⌖ 5.09 AIC · ⊞ 5.7K
Comment /review to run again
| t.Error("Expected created entry to have the given inputs") | ||
| if inputs != nil { | ||
| t.Error("Expected nil inputs for missing entry") | ||
| } |
There was a problem hiding this comment.
The critical no-op guard path — an entry that exists but has an empty SHA — is not tested: the new test only covers the !exists branch; the entry.SHA == "" branch (the exact P0 regression scenario) is untested.
💡 Suggested test
Add a test case that exercises the entry.SHA == "" path for all three affected methods:
// Verify that an entry with an empty SHA doesn't get metadata written to it.
// This was the root cause of the P0: SetReleasedAt created shell entries that
// failed validateUpdateSHAEntries.
func TestSetMethodsNoOpOnEmptySHAEntry(t *testing.T) {
cache := NewActionCache(t.TempDir())
// Manually inject an empty-SHA entry (simulates the pre-fix bug scenario)
cache.Entries["owner/action@v9"] = ActionCacheEntry{Repo: "owner/action", Version: "v9"}
cache.SetReleasedAt("owner/action", "v9", time.Now())
cache.SetInputs("owner/action", "v9", map[string]*ActionYAMLInput{"k": {}})
cache.SetActionDescription("owner/action", "v9", "desc")
// None of the above should mark the cache dirty or write metadata
if cache.dirty {
t.Error("cache should not be dirty after no-op Set calls on empty-SHA entry")
}
entry := cache.Entries["owner/action@v9"]
if entry.ReleasedAt != nil || entry.Inputs != nil || entry.ActionDescription != "" {
t.Error("metadata should not be written to an entry with empty SHA")
}
}Without this test, a future refactor that accidentally removes the entry.SHA == "" guard would not be caught by CI.
There was a problem hiding this comment.
Added in c007ddf: TestActionCache_SettersNoOpOnEmptySHAEntry now exercises the entry.SHA == "" branch for all three setters and verifies no metadata is written and cache dirty state stays false.
| actionCacheLog.Printf("No existing cache entry with SHA for key=%s, skipping inputs update", key) | ||
| return | ||
| } | ||
| entry.Inputs = inputs |
There was a problem hiding this comment.
SetInputs accepts a nil inputs map and silently wipes cached inputs for a valid SHA entry: there is no guard against nil, so a caller can destroy previously-cached inputs without triggering any warning.
💡 Suggested fix
Add a nil guard consistent with how the empty-SHA guard is handled:
func (c *ActionCache) SetInputs(repo, version string, inputs map[string]*ActionYAMLInput) {
if inputs == nil {
return // nothing to store; avoid clearing existing cached inputs
}
key := formatActionCacheKey(repo, version)
entry, exists := c.Entries[key]
if !exists || entry.SHA == "" {
...
}The existing callsite in safe_outputs_actions.go:238 already guards with actionYAML.Inputs != nil before calling SetInputs, but the method itself should defend against nil to avoid silent data loss if future callers omit that guard.
There was a problem hiding this comment.
Implemented in c007ddf: SetInputs now returns early on nil input maps, and TestActionCache_SetInputsNilNoOp verifies existing cached inputs are preserved and no dirty write is triggered.
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Ran the pr-finisher pass. I addressed the in-scope review feedback in c007ddf and completed local validation ( |
CI on
mainhas been failing 90% of runs since ~04:41Z on 2026-07-27, broken across two jobs:Integration: CLI Compile & Poutineandupdate.Root causes & fixes
1. Wrong test assertion (
compile_integration_test.go)TestCompileSafeOutputsActionswas assertingsafe_output_handler_manager.cjsexists in compiled output; the compiler actually emitsprocess_safe_outputs.cjs.2. Empty-SHA entries written to
actions-lock.json(action_cache.go)UpdateActionscallsSetReleasedAt(repo, latestVersion, publishedAt)during cooldown checks for the target version of an upgrade. If the action stays on the old version (cooldown skipped),Setwith a valid SHA is never called for that target version — butSetReleasedAthad already created a shellActionCacheEntry{Repo, Version}with empty SHA.actionCache.Save()then persisted those entries, causingvalidateUpdateSHAEntriesto fail onsetup-uv@v9.0.0,login-action@v4.5.1,ruby/setup-ruby@v1.321.0.Fix:
SetReleasedAt,SetInputs, andSetActionDescriptionare now no-ops when called for a key that doesn't exist or has an empty SHA.3. Stale SHAs in
actions-lock.jsonTwo actions had SHAs that couldn't self-heal via
UpdateActionsdue to downgrade protection:actions-ecosystem/action-add-labels@v1.1.3— tag was force-pushed to a different commitsafedep/pmg@v1— moving major tag now points to a different commitBoth SHAs updated manually to match what GitHub resolves.
4. Lock file drift (
check-workflow-drift)Recompiled
smoke-codex,dataflow-pr-discussion-dataset, andhippo-embedlock files to reflect the corrected SHAs.