Skip to content

Commit 435f999

Browse files
mruegclaude
andauthored
Capture stderr when removing app data (#7321)
remove_data ran rm without capturing stderr, so on a CalledProcessError procerr.stderr was None and calling strip() on it raised an AttributeError instead of logging the error. This aborted app uninstall and backup restore with an unexpected exception. Pipe stderr and use a clean environment, as remove_folder already does. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 92a055d commit 435f999

2 files changed

Lines changed: 32 additions & 3 deletions

File tree

‎supervisor/apps/utils.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
SECURITY_PROFILE,
1616
)
1717
from ..docker.const import Capabilities
18+
from ..utils import clean_env
1819

1920
if TYPE_CHECKING:
2021
from .model import AppModel
@@ -103,7 +104,12 @@ def remove_data(folder: Path) -> None:
103104
"""
104105
try:
105106
subprocess.run(
106-
["rm", "-rf", str(folder)], stdout=subprocess.DEVNULL, text=True, check=True
107+
["rm", "-rf", str(folder)],
108+
stdout=subprocess.DEVNULL,
109+
stderr=subprocess.PIPE,
110+
env=clean_env(),
111+
text=True,
112+
check=True,
107113
)
108114
except OSError as err:
109115
error_msg = str(err)

‎tests/apps/test_utils.py‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
"""Test app utility functions."""
22

3-
from unittest.mock import MagicMock
3+
from pathlib import Path
4+
import subprocess
5+
from typing import Any
6+
from unittest.mock import MagicMock, patch
47

58
import pytest
69

710
from supervisor.apps.model import AppModel
8-
from supervisor.apps.utils import rating_security
11+
from supervisor.apps.utils import rating_security, remove_data
912
from supervisor.const import ATTR_PORTS, ROLE_DEFAULT, SECURITY_DEFAULT
1013

1114

@@ -44,3 +47,23 @@ def test_rating_security_network_exposure(
4447
app.with_full_access = False
4548

4649
assert rating_security(app) == expected
50+
51+
52+
def test_remove_data_process_error(
53+
caplog: pytest.LogCaptureFixture, tmp_path: Path
54+
) -> None:
55+
"""Test remove data logs the rm error output instead of raising."""
56+
57+
def mock_run(args: list[str], **kwargs: Any) -> None:
58+
# Like subprocess.run, stderr is only captured when piped
59+
stderr = (
60+
"rm: cannot remove: Busy\n"
61+
if kwargs.get("stderr") == subprocess.PIPE
62+
else None
63+
)
64+
raise subprocess.CalledProcessError(1, args, stderr=stderr)
65+
66+
with patch("supervisor.apps.utils.subprocess.run", side_effect=mock_run):
67+
remove_data(tmp_path)
68+
69+
assert "Can't remove app data: rm: cannot remove: Busy" in caplog.text

0 commit comments

Comments
 (0)