Skip to content

fix(upload_assets): resolve staged assets via a single GH_AW_ASSETS_DIR#40122

Merged
dsyme merged 3 commits into
mainfrom
fix-upload-assets-path-mismatch
Jun 18, 2026
Merged

fix(upload_assets): resolve staged assets via a single GH_AW_ASSETS_DIR#40122
dsyme merged 3 commits into
mainfrom
fix-upload-assets-path-mismatch

Conversation

@dsyme

@dsyme dsyme commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

test

Generated by PR Description Updater for issue #40122 · 84.1 AIC · ⌖ 10.3 AIC · ⊞ 4.5K ·

…-prefix mismatch (#39885)

The upload_assets (Push assets) job was failing with
  ERR_SYSTEM: Asset file not found: .../safeoutputs/assets/<file>.png
even though the agent job succeeded, because assets were staged under a
different base prefix than the job was reading from.

Prior fixes (#39900, #40062) aligned individual paths, but the consumer
still derived a single assetsDir — so any remaining producer/consumer
prefix disagreement (e.g. RUNNER_TEMP=/home/runner/work/_temp vs the
artifact download path /tmp/gh-aw) still hard-failed the whole job.

Fix: build a de-duplicated list of candidate staging directories
  1. parent of GH_AW_AGENT_OUTPUT (where the artifact was downloaded)
  2. RUNNER_TEMP/gh-aw (where the MCP handler staged the file at runtime)
  3. /tmp/gh-aw (canonical fallback)

The first candidate that contains the file wins. The existing fail-soft
behaviour (warn per missing file, only fail when all are missing) is
preserved. The missing-file warning now lists every directory searched.

Adds a regression test for the cross-prefix case.

Closes #39885
Copilot AI review requested due to automatic review settings June 18, 2026 18:11
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Comment Memory

CI lights the path
Green checks bloom at dawn
Quiet bots still sing

Note

This comment is managed by comment memory.

It stores persistent context for this thread in the code block at the top of this comment.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

Generated by 🧪 Smoke CI for issue #40122 ·

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes recurring upload_assets job failures caused by path-prefix mismatches between where assets are staged at agent runtime (often under RUNNER_TEMP/gh-aw/...) and where downstream jobs download artifacts (often under /tmp/gh-aw/...). It updates the asset consumer to search across multiple candidate staging directories so asset publication is resilient to these prefix differences.

Changes:

  • Update upload_assets.cjs to build a de-duplicated list of candidate safeoutputs/assets directories and search them in order per asset.
  • Improve missing-asset warnings to include all searched directories while preserving fail-soft behavior (only fail when all declared assets are missing).
  • Add a regression test covering the cross-prefix staging scenario.
Show a summary per file
File Description
actions/setup/js/upload_assets.cjs Switch from a single derived assetsDir to ordered multi-candidate asset resolution to avoid path-prefix mismatches.
actions/setup/js/upload_assets.test.cjs Add regression coverage for assets staged under RUNNER_TEMP while GH_AW_AGENT_OUTPUT points elsewhere.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 2

Comment thread actions/setup/js/upload_assets.cjs Outdated
Comment thread actions/setup/js/upload_assets.test.cjs Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@dsyme

dsyme commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

Adjusting this fix

@dsyme dsyme marked this pull request as draft June 18, 2026 20:42
@dsyme dsyme marked this pull request as ready for review June 18, 2026 21:19
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR #40122 does not have the 'implementation' label and has 0 new lines of code in business logic directories (≤100 threshold). Neither enforcement condition is met.

…ching

Replace the multi-candidate staging-dir search with a single source of
truth. The download-artifact step in the upload_assets job writes the
safe-outputs assets to a fixed path, and the Go generator now passes that
exact path to the consumer via GH_AW_ASSETS_DIR. Add constants.TmpGhAwAssetsDir
so the download path and the env var can never drift.

This removes the GH_AW_AGENT_OUTPUT-derived path indirection that caused the
phantom-asset failures (#39885): GH_AW_AGENT_OUTPUT belongs to a separate
artifact and its directory is decoupled from where assets are downloaded.
@dsyme dsyme changed the title fix(upload_assets): search all candidate staging dirs to resolve path-prefix mismatch fix(upload_assets): resolve staged assets via a single GH_AW_ASSETS_DIR Jun 18, 2026
@github-actions github-actions Bot mentioned this pull request Jun 18, 2026
@dsyme dsyme merged commit 3167e4a into main Jun 18, 2026
44 checks passed
@dsyme dsyme deleted the fix-upload-assets-path-mismatch branch June 18, 2026 21:27

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /diagnose and /tdd — approving with suggestions on test coverage and long-term design.

📋 Key Themes & Highlights

Key Themes

  • /diagnose: The multi-candidate search is a clean structural fix vs. the prior single-path approach, and the PR description accurately traces the root cause. The underlying producer-consumer path contract is still implicit — worth tracking as a follow-up.
  • /tdd: The regression test is well-targeted and exercises real filesystem operations. Two gaps: (1) priority ordering between candidates is untested, (2) the core.info diagnostic log isn't asserted and could be silently removed.

Positive Highlights

  • ✅ De-duplication via new Set correctly handles the case where agentOutputFile's parent is /tmp/gh-aw — no double-check of the same path.
  • ✅ Fail-soft semantics (warn per missing file, only fail when all are absent) are fully preserved.
  • ✅ The new warning message now surfaces every directory searched, which is a meaningful improvement for diagnosability.
  • ✅ The regression test uses real filesystem isolation (mkdtempSync) and a proper finally cleanup.
  • ✅ 194/194 tests passing, including the new regression case.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 69.3 AIC · ⌖ 7.63 AIC · ⊞ 6.9K

fs.existsSync(runnerTempBase) && fs.rmSync(runnerTempBase, { recursive: !0, force: !0 });
fs.existsSync(path.join(process.cwd(), targetFile)) && fs.unlinkSync(path.join(process.cwd(), targetFile));
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The regression test covers the core fix, but the priority order (agent-output dir → RUNNER_TEMP → /tmp) is an implicit contract with no test to guard it.

💡 Suggested additional test case

Add a second it that stages the file in both the agent-output directory and RUNNER_TEMP, then asserts the agent-output-relative copy was the one used (e.g. via SHA or a sentinel byte). Without this, a future reordering of candidateBaseDirs would pass all existing tests silently.

it("should prefer agent-output dir over RUNNER_TEMP when both contain the asset", async () => {
  // stage one file in tempBase/safeoutputs/assets/ (agent output dir)
  // stage a different file at runnerTempBase/gh-aw/safeoutputs/assets/
  // assert the agent-output-dir version was uploaded (check its SHA)
});

Comment thread actions/setup/js/upload_assets.cjs Outdated
}
candidateBaseDirs.push("/tmp/gh-aw");
// Build the per-directory assets paths, de-duplicated while preserving order.
const assetsDirs = [...new Set(candidateBaseDirs.map(dir => path.join(dir, "safeoutputs", "assets")))];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnose] This is the third targeted fix for the producer-consumer path-prefix mismatch (after #39900 and #40062). The multi-candidate search is structurally sound, but the root coupling — producer and consumer never agreeing on a base path — remains.

💡 More permanent alternative

The MCP upload_asset handler already knows exactly where it staged each file. If it wrote the staging base directory (or the full resolved path per asset) into agent_output.json alongside the fileName, this consumer could read it directly — eliminating the need for candidate-list guessing and making the contract explicit.

This is a larger change, but it would permanently close the recurrence vector and remove the /tmp/gh-aw hardcoded fallback.

});
try {
await executeScript();
expect(mockCore.setFailed).not.toHaveBeenCalled();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The test asserts setFailed was not called and upload_count equals 1, which is the right outcome check. Consider also asserting that core.info was called with the expected candidate paths — this locks in the diagnostic logging that will help future debugging if the mismatch recurs.

💡 Example assertion
const infoSearchCall = mockCore.info.mock.calls.find(
  ([msg]) => msg.startsWith("Searching for staged assets in:")
);
expect(infoSearchCall).toBeDefined();
expect(infoSearchCall[0]).toContain(runnerTempBase);

This also guards against accidental removal of the info log, which is valuable for diagnosing future environment-specific path mismatches.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 100/100 — Excellent

Analyzed 1 test(s): 1 design, 0 implementation, 0 guideline violation(s).

📊 Metrics & Test Classification (1 test analyzed)
Metric Value
New/modified tests analyzed 1
✅ Design tests (behavioral contracts) 1 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 1 (100%)
Duplicate test clusters 0
Test inflation detected No (37 test lines / 32 prod lines = 1.16×)
🚨 Coding-guideline violations 0
Test File Classification Issues Detected
should find assets staged under RUNNER_TEMP when agent output dir differs actions/setup/js/upload_assets.test.cjs:176 ✅ Design ⚠️ Minor: conditional assertion (see Flagged section)

Go: 0 (*_test.go); JavaScript: 1 (*.test.cjs).

⚠️ Flagged Tests — Non-blocking Observations (1 note)

should find assets staged under RUNNER_TEMP when agent output dir differs (actions/setup/js/upload_assets.test.cjs:176)

  • Conditional assertion: uploadCountCall && expect(uploadCountCall[1]).toBe("1")) — if uploadCountCall is falsy the .toBe("1") silently never runs. In practice the preceding expect(uploadCountCall).toBeDefined() makes this safe (Array.find returns undefined not null), but the idiom is fragile and reads as machine-generated code. Prefer an unconditional assertion after the toBeDefined() check.
  • Minifier-style idioms: { recursive: !0 }, void 0 === prevRunnerTemp — these are valid JavaScript but suggest AI-generated/bundled output. Consider using plain true and === undefined for readability.

These are non-blocking style observations and do not affect the verdict.

Verdict

Check passed. 0% implementation tests (threshold: 30%). The single new test directly exercises the behavioral contract fixed in this PR: assets staged under RUNNER_TEMP/gh-aw/safeoutputs/assets/ are found and uploaded successfully, with correct upload_count output. It also exercises a git error path (rev-parse throwing for a non-existent branch). Mocking targets (mockCore, mockExec.exec) are external I/O — no business-logic mocking.

References:

🧪 Test quality analysis by Test Quality Sentinel · 71.7 AIC · ⌖ 10 AIC · ⊞ 8.3K ·

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 100/100. Test quality is acceptable — 0% of new tests are implementation tests (threshold: 30%).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants