-
Notifications
You must be signed in to change notification settings - Fork 6
fix: handle missing docker-compose.yml during cleanup gracefully #741
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: handle missing docker-compose.yml during cleanup gracefully #741
Conversation
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
|
Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded. |
|
📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤 |
|
💫 TO BE CONTINUED... Smoke Claude failed! Our hero faces unexpected challenges... |
C++ Build Test Results
Overall: PASS ✅ All C++ projects built successfully.
|
Build Test: Deno - BLOCKED❌ Unable to complete Deno tests due to AWF credential hiding incompatibility Error DetailsDeno tests fail with the following error: Root CauseAWF's credential hiding mechanism mounts
This causes Deno to fail during initialization before any tests can run. Test Status
Overall: BLOCKED Recommended Solutions
Note: Label
|
Node.js Build Test Results ✅All Node.js projects tested successfully!
Overall: PASS All projects installed dependencies and ran tests successfully.
|
|
Smoke Test Results (Run 21935968806) Last 2 merged PRs:
✅ GitHub MCP: Retrieved PR data Status: PASS cc
|
Build Test: Bun - Results
Overall: PASS ✅ All Bun projects built and tested successfully.
|
🔴 Rust Build Test - FAILEDError: Rust toolchain not available in workflow environment.
Overall: FAIL Error DetailsThe workflow environment does not have the Rust toolchain installed. Required Fix: The workflow needs to install Rust before running tests: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source $HOME/.cargo/envOr use the
|
Go Build Test Results
Overall: PASS All Go projects built and tested successfully.
|
.NET Build Test Results
Overall: PASS ✅ All .NET projects successfully restored, built, and ran with expected output.
|
Chroot Version Comparison Test Results
Overall Result: ❌ Tests Failed Chroot mode successfully binds host runtimes, but version mismatches indicate the container is using its own bundled versions instead of host binaries. Only Go matched between host and chroot environments.
|
Security Review: Network Cleanup Gap in Fallback PathIssue: Network Resource Leak in Fallback CleanupThe fallback cleanup mechanism added in this PR has a network cleanup gap that could create security vulnerabilities. ProblemWhen File: // Stop and remove containers by name
const containerNames = ['awf-agent', 'awf-squid'];
for (const name of containerNames) {
try {
// Check if container exists
const { stdout } = await execa('docker', ['ps', '-aq', '-f', `name=^${name}$`]);
if (stdout.trim()) {
logger.debug(`Stopping container: ${name}`);
await execa('docker', ['rm', '-f', name], { stdio: 'inherit' });
}
} catch (err) {
logger.debug(`Could not stop container ${name}:`, err);
}
}However, the network is not removed in this fallback path. Security Impact
Comparison with Normal PathNormal cleanup ( await execa('docker', ['compose', 'down', '-v'], {
cwd: workDir,
stdio: 'inherit',
});
Fallback cleanup:
RecommendationAdd network cleanup to the fallback path to maintain security parity: } else {
// Fallback: compose file missing, stop containers by name
logger.debug('Compose file not found, stopping containers by name');
// Stop and remove containers by name
const containerNames = ['awf-agent', 'awf-squid'];
for (const name of containerNames) {
try {
const { stdout } = await execa('docker', ['ps', '-aq', '-f', `name=^${name}$`]);
if (stdout.trim()) {
logger.debug(`Stopping container: ${name}`);
await execa('docker', ['rm', '-f', name], { stdio: 'inherit' });
}
} catch (err) {
logger.debug(`Could not stop container ${name}:`, err);
}
}
// Also remove the network to match docker compose down behavior
try {
await execa('docker', ['network', 'rm', 'awf-net'], { reject: false });
logger.debug('Removed awf-net network');
} catch (err) {
logger.debug('Could not remove network:', err);
}
logger.success('Containers stopped successfully');
}SeverityMedium - While not an immediate exploit vector, inconsistent cleanup could leave the firewall in an undefined state. The security boundary relies on proper teardown of all Docker resources and iptables rules. Network persistence breaks this assumption. Note: The existing code intentionally keeps the network across runs for performance (see comment in
|
Java Build Test Results ✅All Java projects compiled and tested successfully through the AWF firewall.
Overall: PASS All compilation and test execution completed successfully with Maven proxy configuration.
|
c7ad1ac
into
claude/fix-github-actions-workflow-yet-again
* Initial plan * fix: prevent .cargo volume/tmpfs mount conflict in chroot mode Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> * fix: handle missing docker-compose.yml during cleanup gracefully (#741) * Initial plan * fix: handle missing docker-compose.yml during cleanup gracefully Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
* Initial plan * fix: replace /dev/null mounts with tmpfs for credential hiding This fixes Docker mount errors like "read-only file system" that occur when mounting /dev/null over credential files whose parent directories don't exist in the container's filesystem. The solution uses tmpfs mounts instead, which create empty in-memory filesystems that overlay directories without requiring the target path to exist first. Changes: - Normal mode: Hide credential directories (~/.docker, ~/.ssh, ~/.aws, etc.) using tmpfs - Chroot mode: Hide credential directories at /host paths using tmpfs - Updated DockerService type to include tmpfs property - Updated tests to verify tmpfs behavior instead of /dev/null mounts - Fixed config mutation bug by using local variable instead of mutating config object Closes the GitHub Actions failure where cargo credentials mounting failed with: "error mounting "/dev/null" to rootfs at "/host/home/runner/.cargo/credentials": create mountpoint ... read-only file system" Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> * fix: prevent .cargo volume/tmpfs mount conflict in chroot mode (#740) * Initial plan * fix: prevent .cargo volume/tmpfs mount conflict in chroot mode Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> * fix: handle missing docker-compose.yml during cleanup gracefully (#741) * Initial plan * fix: handle missing docker-compose.yml during cleanup gracefully Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
* Initial plan * fix: only hide credential files if parent directory exists Prevents Docker mount errors when credential file parent directories don't exist on the host. This fixes the "read-only file system" error when Docker tries to create mountpoints for non-existent parents. The fix checks if the parent directory exists before adding /dev/null mounts for credential files in both normal and chroot modes. This maintains security while avoiding mount failures. Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> * fix: replace /dev/null mounts with tmpfs for credential hiding (#738) * Initial plan * fix: replace /dev/null mounts with tmpfs for credential hiding This fixes Docker mount errors like "read-only file system" that occur when mounting /dev/null over credential files whose parent directories don't exist in the container's filesystem. The solution uses tmpfs mounts instead, which create empty in-memory filesystems that overlay directories without requiring the target path to exist first. Changes: - Normal mode: Hide credential directories (~/.docker, ~/.ssh, ~/.aws, etc.) using tmpfs - Chroot mode: Hide credential directories at /host paths using tmpfs - Updated DockerService type to include tmpfs property - Updated tests to verify tmpfs behavior instead of /dev/null mounts - Fixed config mutation bug by using local variable instead of mutating config object Closes the GitHub Actions failure where cargo credentials mounting failed with: "error mounting "/dev/null" to rootfs at "/host/home/runner/.cargo/credentials": create mountpoint ... read-only file system" Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> * fix: prevent .cargo volume/tmpfs mount conflict in chroot mode (#740) * Initial plan * fix: prevent .cargo volume/tmpfs mount conflict in chroot mode Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> * fix: handle missing docker-compose.yml during cleanup gracefully (#741) * Initial plan * fix: handle missing docker-compose.yml during cleanup gracefully Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
* Initial plan * fix: create .copilot/logs mountpoint before docker mount The agent container mount at ~/.copilot:ro followed by ~/agent-logs:~/.copilot/logs:rw fails in GitHub Actions because Docker cannot create the logs subdirectory inside a read-only mount. Fix by creating ~/.copilot/logs on the host before mounting, so Docker doesn't need to create the mountpoint inside the read-only parent. This affects GitHub Actions runners where ~/.copilot doesn't exist before AWF runs. Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> * fix: only hide credential files if parent directory exists (#737) * Initial plan * fix: only hide credential files if parent directory exists Prevents Docker mount errors when credential file parent directories don't exist on the host. This fixes the "read-only file system" error when Docker tries to create mountpoints for non-existent parents. The fix checks if the parent directory exists before adding /dev/null mounts for credential files in both normal and chroot modes. This maintains security while avoiding mount failures. Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> * fix: replace /dev/null mounts with tmpfs for credential hiding (#738) * Initial plan * fix: replace /dev/null mounts with tmpfs for credential hiding This fixes Docker mount errors like "read-only file system" that occur when mounting /dev/null over credential files whose parent directories don't exist in the container's filesystem. The solution uses tmpfs mounts instead, which create empty in-memory filesystems that overlay directories without requiring the target path to exist first. Changes: - Normal mode: Hide credential directories (~/.docker, ~/.ssh, ~/.aws, etc.) using tmpfs - Chroot mode: Hide credential directories at /host paths using tmpfs - Updated DockerService type to include tmpfs property - Updated tests to verify tmpfs behavior instead of /dev/null mounts - Fixed config mutation bug by using local variable instead of mutating config object Closes the GitHub Actions failure where cargo credentials mounting failed with: "error mounting "/dev/null" to rootfs at "/host/home/runner/.cargo/credentials": create mountpoint ... read-only file system" Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> * fix: prevent .cargo volume/tmpfs mount conflict in chroot mode (#740) * Initial plan * fix: prevent .cargo volume/tmpfs mount conflict in chroot mode Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> * fix: handle missing docker-compose.yml during cleanup gracefully (#741) * Initial plan * fix: handle missing docker-compose.yml during cleanup gracefully Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
There was a problem hiding this comment.
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 adds defensive error handling to stopContainers() to prevent failures when docker-compose.yml is missing during cleanup. The changes implement a fallback mechanism that stops containers by name using docker rm -f when the compose file is unavailable.
Changes:
- Added existence checks for workDir and docker-compose.yml before attempting
docker compose down - Implemented fallback to stop containers by name (awf-agent, awf-squid) when compose file is missing
- Added individual try-catch blocks for each container to prevent cascading failures
Comments suppressed due to low confidence (1)
src/docker-manager.ts:1314
- The comment "Normal path: use docker compose down" is misleading. Due to the security fix at line 1149 that deletes docker-compose.yml immediately after containers start, this path will rarely execute during normal operation. The "fallback" path (lines 1314-1332) is actually the normal execution path.
Consider updating comments to reflect this reality:
- Line 1307: "Compose file exists (e.g., startup failed before deletion): use docker compose down"
- Line 1314: "Normal path: compose file was deleted for security, stop containers by name"
// Normal path: use docker compose down
await execa('docker', ['compose', 'down', '-v'], {
cwd: workDir,
stdio: 'inherit',
});
logger.success('Containers stopped successfully');
} else {
// Fallback: compose file missing, stop containers by name
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Check if workDir and docker-compose.yml exist before using docker compose | ||
| const composeFile = path.join(workDir, 'docker-compose.yml'); | ||
| if (fs.existsSync(workDir) && fs.existsSync(composeFile)) { | ||
| // Normal path: use docker compose down | ||
| await execa('docker', ['compose', 'down', '-v'], { | ||
| cwd: workDir, | ||
| stdio: 'inherit', | ||
| }); | ||
| logger.success('Containers stopped successfully'); | ||
| } else { | ||
| // Fallback: compose file missing, stop containers by name | ||
| logger.debug('Compose file not found, stopping containers by name'); | ||
|
|
||
| // Stop and remove containers by name | ||
| const containerNames = ['awf-agent', 'awf-squid']; | ||
| for (const name of containerNames) { | ||
| try { | ||
| // Check if container exists | ||
| const { stdout } = await execa('docker', ['ps', '-aq', '-f', `name=^${name}$`]); | ||
| if (stdout.trim()) { | ||
| logger.debug(`Stopping container: ${name}`); | ||
| await execa('docker', ['rm', '-f', name], { stdio: 'inherit' }); | ||
| } | ||
| } catch (err) { | ||
| logger.debug(`Could not stop container ${name}:`, err); | ||
| } | ||
| } | ||
|
|
||
| logger.success('Containers stopped successfully'); | ||
| } |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic assumes the fallback path (stopping containers by name) is for error scenarios, but due to the security fix at line 1149 that deletes docker-compose.yml immediately after containers start, the compose file will NOT exist during normal cleanup. This makes the fallback path the primary execution path, not a true fallback.
Consider either:
- Removing the compose file check and always using docker rm to stop containers by name (simpler, more predictable)
- Keeping the compose file but restricting its permissions instead of deleting it
- Adding a comment explaining that this is the expected normal path, not just a fallback
The current implementation works correctly but the code structure and comments are misleading about which path is the normal case versus the exceptional case.
This issue also appears on line 1307 of the same file.
| // Stop and remove containers by name | ||
| const containerNames = ['awf-agent', 'awf-squid']; | ||
| for (const name of containerNames) { | ||
| try { | ||
| // Check if container exists | ||
| const { stdout } = await execa('docker', ['ps', '-aq', '-f', `name=^${name}$`]); | ||
| if (stdout.trim()) { | ||
| logger.debug(`Stopping container: ${name}`); | ||
| await execa('docker', ['rm', '-f', name], { stdio: 'inherit' }); | ||
| } | ||
| } catch (err) { | ||
| logger.debug(`Could not stop container ${name}:`, err); | ||
| } | ||
| } | ||
|
|
||
| logger.success('Containers stopped successfully'); |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error handling in the fallback path swallows all errors with individual try-catch blocks (lines 1320-1329). This means if both containers fail to stop (e.g., Docker daemon is down), the function will still log "Containers stopped successfully" and return without error.
Consider tracking whether at least one container operation succeeded, or accumulating errors and throwing if no containers could be stopped. For example:
let stoppedAny = false;
const errors: Error[] = [];
for (const name of containerNames) {
try {
// ... existing logic ...
stoppedAny = true;
} catch (err) {
errors.push(err);
logger.debug(`Could not stop container ${name}:`, err);
}
}
if (!stoppedAny && errors.length > 0) {
throw new Error(`Failed to stop any containers: ${errors.map(e => e.message).join(', ')}`);
}This ensures the outer error handler and calling code are aware when cleanup truly fails.
stopContainers()fails with "no configuration file provided: not found" when the work directory or docker-compose.yml is missing during cleanup, causing GitHub Actions workflows to exit with errors.Changes
stopContainers()to verify workDir and docker-compose.yml exist before runningdocker compose down -vawf-agent,awf-squid) usingdocker rm -fwhen compose file is unavailableImplementation
This ensures cleanup succeeds regardless of workDir state, preventing workflow failures when the environment is in an unexpected state.