fix(setup): clean up root-owned /tmp/awf-*-chroot-home directories#41852
Conversation
When AWF ran with --enable-host-access (using sudo -E awf), it created
/tmp/awf-{timestamp}-chroot-home directories containing root-owned files.
The Copilot CLI cleanup path (rimrafSync via Node.js internals) ran as the
non-root runner user and failed with EACCES, causing the code-simplifier
workflow to report 'engine terminated unexpectedly'.
Add sudo rm -rf cleanup for /tmp/awf-*-chroot-home in:
- actions/setup/post.js (post-run cleanup action)
- actions/setup/clean.sh (script-mode mirror of post.js)
- actions/setup/sh/install_copilot_cli.sh (pre-run, alongside existing
sudo chown fix for the .copilot directory)
Closes #41842
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
- Remove redundant nested sudo in install_copilot_cli.sh find command - Rename awfChrootHomeCleanup to awfChrootHomeCleanupResult in post.js - Clarify log message for non-zero exit status in post.js Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
🤖 PR Triage — §28282332784
Rationale: Draft PR fixing root-owned chroot-home cleanup on shared runners (4 files). No CI yet. Promote from draft and ensure CI passes before review.
|
|
✅ Test Quality Sentinel completed test quality analysis. No test files were added or modified in this PR. Test Quality Sentinel skipped. Changed files: actions/setup/clean.sh, actions/setup/post.js, actions/setup/sh/install_copilot_cli.sh, .changeset/patch-fix-awf-chroot-home-cleanup.md |
|
✅ Design Decision Gate 🏗️ completed the design decision gate check. No ADR enforcement needed: PR does not have the 'implementation' label and has 0 new lines of code in business logic directories (threshold: 100). |
|
✅ PR Code Quality Reviewer completed the code quality review. |
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅ |
There was a problem hiding this comment.
Pull request overview
This PR addresses cleanup failures on shared GitHub-hosted runners caused by root-owned AWF chroot-home directories (/tmp/awf-*-chroot-home) left behind after running AWF with --enable-host-access. The change adds best-effort, sudo-based cleanup paths so subsequent runs don’t fail with EACCES during Copilot CLI cleanup.
Changes:
- Add cleanup for
/tmp/awf-*-chroot-homein the setup action post step (actions/setup/post.js). - Mirror the same cleanup behavior for script-mode runs (
actions/setup/clean.sh). - Add pre-run cleanup in the Copilot CLI install script to prevent stale state before the agent starts (
actions/setup/sh/install_copilot_cli.sh).
Show a summary per file
| File | Description |
|---|---|
| actions/setup/sh/install_copilot_cli.sh | Pre-run removal of stale /tmp/awf-*-chroot-home directories to avoid Copilot CLI EACCES cleanup failures. |
| actions/setup/post.js | Post-job cleanup now also attempts to remove AWF chroot-home remnants under /tmp. |
| actions/setup/clean.sh | Script-mode equivalent cleanup now includes removing AWF chroot-home remnants under /tmp. |
| .changeset/patch-fix-awf-chroot-home-cleanup.md | Patch changeset documenting the runner cleanup fix. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 4/4 changed files
- Comments generated: 3
- Review effort level: Low
| // Clean up AWF chroot home directories under /tmp (e.g. /tmp/awf-*-chroot-home). | ||
| // These are created by AWF when running with --enable-host-access on GitHub-hosted runners. | ||
| // Files inside may be owned by root (written by Docker containers or privileged AWF processes), | ||
| // causing EACCES failures if cleanup is attempted without sudo. We use sudo rm -rf to handle | ||
| // these root-owned remnants; failures are non-fatal since cleanup is best-effort. | ||
| const awfChrootHomeCleanupResult = spawnSync("sudo", ["sh", "-c", "rm -rf /tmp/awf-*-chroot-home"], { stdio: "inherit" }); | ||
| if (awfChrootHomeCleanupResult.status === 0) { | ||
| console.log("Cleaned up /tmp/awf-*-chroot-home"); | ||
| } else { | ||
| console.log("Failed to clean /tmp/awf-*-chroot-home or no directories found"); | ||
| } |
| if sudo rm -rf /tmp/awf-*-chroot-home 2>/dev/null; then | ||
| echo "Cleaned up /tmp/awf-*-chroot-home (sudo)" | ||
| fi |
There was a problem hiding this comment.
Skills-Based Review 🧠
Applied /diagnose, /tdd, and /zoom-out — commenting with suggestions, no blocking issues.
📋 Key Themes & Highlights
Key Themes
- Silent failure paths:
post.jsusesconsole.logfor cleanup failures (notwarn/error), and thespawnSyncerror property is not inspected — spawn failures fall silently into the else branch. clean.shinconsistencies: Noelsewarning branch whensudo rm -rffails, and uses a shell-globrm -rfwhileinstall_copilot_cli.shuses the saferfind-based approach.- No regression test: The fixed EACCES code path has no accompanying test; a future refactor could silently reintroduce the failure.
Positive Highlights
- ✅ Root cause correctly identified and addressed at three call sites (
post.js,clean.sh,install_copilot_cli.sh). - ✅ Mirrors the existing
sudo rm -rfpattern already used for/tmp/gh-aw— consistent idiom. - ✅ Non-fatal / best-effort design is appropriate; failures are logged, not thrown.
- ✅ Detailed comments in each file explain the sudo requirement and why root-owned files appear — great for future maintainers.
- ✅ Changeset entry is clear and accurate.
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 57.6 AIC · ⌖ 8.18 AIC · ⊞ 6.6K
| // Files inside may be owned by root (written by Docker containers or privileged AWF processes), | ||
| // causing EACCES failures if cleanup is attempted without sudo. We use sudo rm -rf to handle | ||
| // these root-owned remnants; failures are non-fatal since cleanup is best-effort. | ||
| const awfChrootHomeCleanupResult = spawnSync("sudo", ["sh", "-c", "rm -rf /tmp/awf-*-chroot-home"], { stdio: "inherit" }); |
There was a problem hiding this comment.
[/diagnose] spawnSync can fail to spawn the child process entirely (e.g., if sudo is absent from PATH), returning { error: Error, status: null }. Checking only .status means a spawn-level failure silently falls into the else branch without surfacing the underlying error.
💡 Suggested fix
const awfChrootHomeCleanupResult = spawnSync(
"sudo", ["sh", "-c", "rm -rf /tmp/awf-*-chroot-home"],
{ stdio: "inherit" }
);
if (awfChrootHomeCleanupResult.error) {
console.warn(`Warning: failed to spawn AWF chroot-home cleanup: ${awfChrootHomeCleanupResult.error.message}`);
} else if (awfChrootHomeCleanupResult.status === 0) {
console.log("Cleaned up /tmp/awf-*-chroot-home");
} else {
console.warn(`Warning: AWF chroot-home cleanup exited with status ${awfChrootHomeCleanupResult.status}`);
}This mirrors the defensive err.message pattern already used in the rimrafSync error handler above.
@copilot please address this.
| if (awfChrootHomeCleanupResult.status === 0) { | ||
| console.log("Cleaned up /tmp/awf-*-chroot-home"); | ||
| } else { | ||
| console.log("Failed to clean /tmp/awf-*-chroot-home or no directories found"); |
There was a problem hiding this comment.
[/diagnose] The failure branch uses console.log — so a cleanup failure is indistinguishable from normal output in runner logs. Use console.warn (or console.error) to make this visible when scanning logs for problems.
💡 Suggested change
} else {
console.warn(`Warning: failed to clean /tmp/awf-*-chroot-home (exit ${awfChrootHomeCleanupResult.status})`);
}The existing rimrafSync failure path in this file already uses console.error("Warning: ...") — aligning here keeps the log severity consistent.
@copilot please address this.
| # causing EACCES failures if cleanup is attempted without sudo. | ||
| if sudo rm -rf /tmp/awf-*-chroot-home 2>/dev/null; then | ||
| echo "Cleaned up /tmp/awf-*-chroot-home (sudo)" | ||
| fi |
There was a problem hiding this comment.
[/diagnose] The if block silently ignores a non-zero exit code — 2>/dev/null already suppresses stderr, and there is no else branch. post.js at least logs a message on failure; clean.sh drops the signal entirely, making future debugging harder.
💡 Suggested change
if sudo rm -rf /tmp/awf-*-chroot-home 2>/dev/null; then
echo "Cleaned up /tmp/awf-*-chroot-home (sudo)"
else
echo "Warning: failed to clean /tmp/awf-*-chroot-home" >&2
fiThis mirrors the echo "Warning: failed to clean up ..." >&2 pattern already used at line 41 of this file.
@copilot please address this.
| # These are created by AWF when running with --enable-host-access on GitHub-hosted runners. | ||
| # Files inside may be owned by root (written by Docker containers or privileged AWF processes), | ||
| # causing EACCES failures if cleanup is attempted without sudo. | ||
| if sudo rm -rf /tmp/awf-*-chroot-home 2>/dev/null; then |
There was a problem hiding this comment.
[/zoom-out] clean.sh uses a shell-glob rm -rf approach while install_copilot_cli.sh (line 54) uses the safer sudo find /tmp -maxdepth 1 -name 'awf-*-chroot-home' -type d -exec rm -rf {} +. The find-based approach is more explicit (only removes directories, never ambiguous if glob expands to a file), and it is already established in this PR. Aligning both scripts reduces divergence.
💡 Suggested change
# Remove AWF chroot home directories under /tmp (e.g. /tmp/awf-*-chroot-home).
if sudo find /tmp -maxdepth 1 -name 'awf-*-chroot-home' -type d -exec rm -rf {} + 2>/dev/null; then
echo "Cleaned up /tmp/awf-*-chroot-home (sudo)"
else
echo "Warning: failed to clean /tmp/awf-*-chroot-home" >&2
fi@copilot please address this.
| // Files inside may be owned by root (written by Docker containers or privileged AWF processes), | ||
| // causing EACCES failures if cleanup is attempted without sudo. We use sudo rm -rf to handle | ||
| // these root-owned remnants; failures are non-fatal since cleanup is best-effort. | ||
| const awfChrootHomeCleanupResult = spawnSync("sudo", ["sh", "-c", "rm -rf /tmp/awf-*-chroot-home"], { stdio: "inherit" }); |
There was a problem hiding this comment.
[/tdd] This bug fix has no accompanying regression test. Without one, the EACCES failure path could silently return if this cleanup code is ever refactored or removed. A unit test that mocks spawnSync and asserts it is called with the correct sudo arguments would guard against regressions.
💡 Sketch of a regression test
it('calls sudo rm -rf for awf chroot-home dirs on cleanup', () => {
const spawnSyncMock = jest.fn().mockReturnValue({ status: 0, error: null });
// inject mock and run the cleanup step
expect(spawnSyncMock).toHaveBeenCalledWith(
'sudo',
['sh', '-c', 'rm -rf /tmp/awf-*-chroot-home'],
expect.objectContaining({ stdio: 'inherit' })
);
});Even a lightweight smoke test prevents silent regressions.
@copilot please address this.
There was a problem hiding this comment.
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
patchdiff.githubusercontent.com
To allow these domains, add them to the
network.allowedlist in your workflow frontmatter:
network:
allowed:
- defaults
- "patchdiff.githubusercontent.com"See Network Configuration for more information.
🔎 Code quality review by PR Code Quality Reviewer · 91.1 AIC · ⌖ 6.91 AIC · ⊞ 5.2K
| if (awfChrootHomeCleanupResult.status === 0) { | ||
| console.log("Cleaned up /tmp/awf-*-chroot-home"); | ||
| } else { | ||
| console.log("Failed to clean /tmp/awf-*-chroot-home or no directories found"); |
There was a problem hiding this comment.
Failure path uses console.log instead of console.error, so a genuine sudo failure blends into normal output and won't surface as an error in CI log scanning or ::error:: annotations.
💡 Suggested fix
Change the else branch to match the error-logging convention already used for the tmpDir cleanup on line 119:
} else {
console.error("Failed to clean /tmp/awf-*-chroot-home: sudo returned non-zero");
}Note: the success branch also needs attention (flagged separately) — when no directories match, rm -rf with -f exits 0, so the else branch is only reachable when sudo itself fails. That makes correct error logging here especially important for diagnosing sudo policy issues on runners.
| # causing EACCES failures if cleanup is attempted without sudo. | ||
| if sudo rm -rf /tmp/awf-*-chroot-home 2>/dev/null; then | ||
| echo "Cleaned up /tmp/awf-*-chroot-home (sudo)" | ||
| fi |
There was a problem hiding this comment.
When sudo rm -rf returns non-zero (actual sudo failure, not the no-match case), there is no warning emitted — the script silently continues. The tmpDir cleanup just above (lines 34–42) uses a three-branch pattern ending with echo "Warning: ..." >&2. This new block is missing that fallback, making it impossible to distinguish a successful no-op from a failed cleanup in CI logs.
💡 Suggested fix
Add an else branch consistent with the established pattern in this file:
if sudo rm -rf /tmp/awf-*-chroot-home 2>/dev/null; then
echo "Cleaned up /tmp/awf-*-chroot-home (sudo)"
else
echo "Warning: failed to clean up /tmp/awf-*-chroot-home" >&2
fiNote: the success branch will still print even when no directories matched (separate issue already flagged). The else here specifically guards against actual sudo/rm failure.
|
@copilot run pr-finisher skill |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
@copilot please run the
|
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

When AWF ran with
sudo -E awf --enable-host-access, it created/tmp/awf-{timestamp}-chroot-homedirectories with root-owned files. On shared runners these persisted across jobs; the Copilot CLI'srimrafSync(Node.js-internal) ran as the non-root runner user and failed:Changes
actions/setup/post.js— after cleaning/tmp/gh-aw, switch chroot-home cleanup tosudo find ... -print+sudo find ... -exec rm -rf -- {} +so behavior is explicit and logs distinguish:actions/setup/clean.sh— mirror the samefind-based cleanup behavior in script mode, including explicit “none found” and failure messagesactions/setup/sh/install_copilot_cli.sh— keep pre-run cleanup and add--inrm -rf -- {}for option-safetyactions/setup/js/chroot_home_cleanup.test.js— add coverage for chroot-home cleanup paths in bothpost.jsandclean.sh(no-match and successful cleanup branches)These changes keep cleanup best-effort while making the behavior and logging accurate and adding test coverage for the new cleanup paths.