Skip to content

fix: avoid docker cleanup before container start - #141

Merged
delete-cloud merged 1 commit into
mainfrom
fix/setup-prevalidation-cleanup
May 12, 2026
Merged

delete-cloud merged 1 commit into
mainfrom
fix/setup-prevalidation-cleanup

Conversation

@delete-cloud

@delete-cloud delete-cloud commented May 12, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • only mark Docker cleanup as needed after setup Docker execution begins or before agent container start
  • tighten snapshot import failure regression test to assert Docker is not invoked
  • add setup pre-validation failure coverage to ensure workspace directories are removed without Docker cleanup

Validation

  • uv run pytest tests/coding_agent/environment/test_docker_workspace_provider.py -k "snapshot_import_fails_before_containers or setup_validation_fails_before_containers" -q
  • uv run pytest tests/coding_agent/environment/test_docker_workspace_provider.py -q
  • uv run basedpyright --level error src/coding_agent/environment/docker_workspace_provider.py tests/coding_agent/environment/test_docker_workspace_provider.py
  • uv run ruff format --check src/coding_agent/environment/docker_workspace_provider.py tests/coding_agent/environment/test_docker_workspace_provider.py
  • git diff --check

Summary by CodeRabbit

  • Bug Fixes

    • Improved Docker workspace provisioning to ensure proper resource cleanup during setup phase failures and configuration validation errors, preventing orphaned containers.
  • Tests

    • Added tests validating workspace cleanup behavior during early setup phase configuration validation failures.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 12, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR extends Docker workspace provisioning with optional callback-based tracking of whether Docker is invoked during the remote setup phase. The callback enables conditional cleanup behavior when setup validation fails before any containers are started.

Changes

Setup-phase Docker invocation tracking callback

Layer / File(s) Summary
Setup-phase callback contract and invocation
coding-agent/src/coding_agent/environment/docker_workspace_provider.py
Added Callable to module imports and introduced optional on_docker_invoked callback parameter to _run_docker_setup_phase_if_configured. Callback is invoked immediately before executing the Docker setup command.
Docker invocation tracking in provisioning
coding-agent/src/coding_agent/environment/docker_workspace_provider.py
Updated provision_cloud_workspace_binding to wrap the setup-phase invocation with a callback that sets docker_cleanup_needed flag only when Docker is actually invoked, deferring flag assignment until setup completes.
Tests for early validation failures
coding-agent/tests/coding_agent/environment/test_docker_workspace_provider.py
Updated snapshot-import failure test to track Docker invocation with docker_run_called flag and verify the workspace directory is empty after invalid base64 error. Added new test validating that remote setup configuration validation failures (invalid remote_phases.setup.network) prevent container startup and leave workspace empty.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • delete-cloud/Coding-Agent-Loop#140: Both PRs track whether Docker setup is invoked to conditionally apply cleanup on pre-container failures.
  • delete-cloud/Coding-Agent-Loop#139: Both PRs modify the remote setup flow; this PR adds detection of Docker invocation via callback while the related PR reorders snapshot import before setup phase.
  • delete-cloud/Coding-Agent-Loop#113: Both PRs modify docker_workspace_provider.py and its test suite; this PR enhances setup-phase hooks while the related PR introduced the Docker workspace provider implementation.

🐰 A callback to track the Docker dance,

When setup validation takes its chance,

Before containers start to play,

Cleanup knows what cleanup must say! 🎪

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main fix: preventing Docker cleanup from occurring before containers are started, which directly aligns with the primary objective.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/setup-prevalidation-cleanup

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@coding-agent/tests/coding_agent/environment/test_docker_workspace_provider.py`:
- Around line 931-933: The pytest.raises assertion uses an unescaped regex which
lets dots match any char; update the match argument in the pytest.raises call to
use a raw string and escape the literal dots so the pattern is exact (e.g.,
r'remote_phases\.setup\.network must be "none" or "bridge"') so the test only
passes for the precise error message referenced in the
test_docker_workspace_provider.py assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1357d8f2-38ac-4abb-ab43-ad76e6a2a16e

📥 Commits

Reviewing files that changed from the base of the PR and between 3cd6bc4 and 4279ff7.

📒 Files selected for processing (2)
  • coding-agent/src/coding_agent/environment/docker_workspace_provider.py
  • coding-agent/tests/coding_agent/environment/test_docker_workspace_provider.py

Comment on lines +931 to +933
with pytest.raises(
ValueError, match='remote_phases.setup.network must be "none" or "bridge"'
):

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the regex-pattern lint warning is resolved for this file.
uv run ruff check coding-agent/tests/coding_agent/environment/test_docker_workspace_provider.py --select RUF043 -n

Repository: delete-cloud/Coding-Agent-Loop

Length of output: 117


🏁 Script executed:

#!/bin/bash
# First, check if the file exists and read the relevant lines
cat -n coding-agent/tests/coding_agent/environment/test_docker_workspace_provider.py | sed -n '920,940p'

Repository: delete-cloud/Coding-Agent-Loop

Length of output: 963


🏁 Script executed:

#!/bin/bash
# Search for the ValueError with this message pattern
rg -n 'remote_phases.*setup.*network must be' coding-agent/

Repository: delete-cloud/Coding-Agent-Loop

Length of output: 643


🏁 Script executed:

#!/bin/bash
# Get more context around where the ValueError is raised
cat -n coding-agent/src/coding_agent/environment/docker_workspace_provider.py | sed -n '1080,1090p'

Repository: delete-cloud/Coding-Agent-Loop

Length of output: 603


🏁 Script executed:

#!/bin/bash
# Check if ruff has RUF043 rule and what it does
python3 <<'EOF'
# Let's verify pytest.raises match behavior
import re
pattern = 'remote_phases.setup.network must be "none" or "bridge"'
test_message = 'remote_phases.setup.network must be "none" or "bridge"'
test_message_bad = 'remote_phasesXsetupXnetwork must be "none" or "bridge"'

# This is how pytest.raises match works - it uses re.search
try:
    if re.search(pattern, test_message):
        print("✓ Pattern matches correct message")
    else:
        print("✗ Pattern does NOT match correct message")
except Exception as e:
    print(f"Error: {e}")

try:
    if re.search(pattern, test_message_bad):
        print("✗ Pattern INCORRECTLY matches bad message (unescaped dots are the problem)")
    else:
        print("✓ Pattern does NOT match bad message")
except Exception as e:
    print(f"Error: {e}")

# Now test with escaped version
escaped_pattern = r'remote_phases\.setup\.network must be "none" or "bridge"'
print("\nWith escaped pattern:")
try:
    if re.search(escaped_pattern, test_message):
        print("✓ Escaped pattern matches correct message")
    else:
        print("✗ Escaped pattern does NOT match correct message")
except Exception as e:
    print(f"Error: {e}")

try:
    if re.search(escaped_pattern, test_message_bad):
        print("✗ Escaped pattern INCORRECTLY matches bad message")
    else:
        print("✓ Escaped pattern does NOT match bad message (correct behavior)")
except Exception as e:
    print(f"Error: {e}")
EOF

Repository: delete-cloud/Coding-Agent-Loop

Length of output: 312


🏁 Script executed:

#!/bin/bash
# Search for other pytest.raises with match parameter in this test file
rg -n "pytest.raises.*match=" coding-agent/tests/coding_agent/environment/test_docker_workspace_provider.py

Repository: delete-cloud/Coding-Agent-Loop

Length of output: 522


🏁 Script executed:

#!/bin/bash
python3 <<'EOF'
import re

patterns = [
    ("docker exec command timed out after 1s", "docker exec command timed out after 1s"),
    ("docker exec command timed out after 1s", "docker execXcommand timed out after 1s"),  # unescaped dots test
    ("Path is outside docker workspace: /etc", "Path is outside docker workspace: /etc"),
    ("Path is outside docker workspace: /etc", "PathXisXoutsideXdockerXworkspaceXXXetc"),  # unescaped dots test
]

for pattern, test_msg in patterns:
    if re.search(pattern, test_msg):
        status = "✓ matches"
    else:
        status = "✗ no match"
    print(f"{status}: '{pattern}' on '{test_msg}'")
EOF

Repository: delete-cloud/Coding-Agent-Loop

Length of output: 460


🏁 Script executed:

#!/bin/bash
# Check the exact behavior with the problematic pattern
python3 <<'EOF'
import re

# The actual pattern from line 932
pattern = 'remote_phases.setup.network must be "none" or "bridge"'

# Test cases
test_cases = [
    ('remote_phases.setup.network must be "none" or "bridge"', "Correct message"),
    ('remote_phasesXsetupXnetwork must be "none" or "bridge"', "Incorrect - dots unescaped match any char"),
]

print("Current UNESCAPED pattern behavior:")
for msg, description in test_cases:
    match = re.search(pattern, msg)
    result = "MATCHES" if match else "no match"
    print(f"  {result}: {description}")

print("\n" + "="*70)

# The suggested fix
escaped_pattern = r'remote_phases\.setup\.network must be "none" or "bridge"'
print("\nSuggested ESCAPED pattern behavior:")
for msg, description in test_cases:
    match = re.search(escaped_pattern, msg)
    result = "MATCHES" if match else "no match"
    print(f"  {result}: {description}")
EOF

Repository: delete-cloud/Coding-Agent-Loop

Length of output: 380


Escape regex metacharacters in pytest.raises(match=...) to prevent unintended matches.

The unescaped dots in the pattern will match any character, allowing the test to pass on incorrect error messages (e.g., 'remote_phasesXsetupXnetwork...'). Use a raw string with escaped dots for precision.

Suggested fix
    with pytest.raises(
-       ValueError, match='remote_phases.setup.network must be "none" or "bridge"'
+       ValueError,
+       match=r'remote_phases\.setup\.network must be "none" or "bridge"',
    ):
📝 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.

Suggested change
with pytest.raises(
ValueError, match='remote_phases.setup.network must be "none" or "bridge"'
):
with pytest.raises(
ValueError,
match=r'remote_phases\.setup\.network must be "none" or "bridge"',
):
🧰 Tools
🪛 Ruff (0.15.12)

[warning] 932-932: Pattern passed to match= contains metacharacters but is neither escaped nor raw

(RUF043)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@coding-agent/tests/coding_agent/environment/test_docker_workspace_provider.py`
around lines 931 - 933, The pytest.raises assertion uses an unescaped regex
which lets dots match any char; update the match argument in the pytest.raises
call to use a raw string and escape the literal dots so the pattern is exact
(e.g., r'remote_phases\.setup\.network must be "none" or "bridge"') so the test
only passes for the precise error message referenced in the
test_docker_workspace_provider.py assertion.

@delete-cloud
delete-cloud merged commit 7b5a4fd into main May 12, 2026
1 check passed
@delete-cloud
delete-cloud deleted the fix/setup-prevalidation-cleanup branch May 12, 2026 13:42
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.

1 participant