Repository: home-assistant/supervisor

Issue:
Manual backup restore not working after 2025.2.4 : 'TarFile' object has no attribute 'slug'
### The problem

Hello, My HA crashed, it won't start anymore, and I would like to restore a recent backup. 
I installed the latest version (OS 14.2) instead and I first asked to recover my backup (Automatic backup 2025.2.4 - Home Assistant, SSL, Duck DNS, NGINX Home Assistant SSL proxy, File editor, Terminal & SSH, Mosquitto broker, 45df7312_zigb - 282 MB). I got the information that HA was recovering my backup. After 3 hours, as it was not finished (usually, it takes 30 minutes) I closed my Firefox window. 
I opened a new Firefox window and there I finished installing a new empty HA application under a new user name. 
I imported my backup. I position myself on it, I select the elements to restore (all or 282 MB) Restore And I have the error message : 'TarFile' object has no attribute 'slug'
I tried with older core (2024-12-0) : same error with recent backups
I tried with older core and older backups: 
- from January 1, 2025 until now : same error ( : 'TarFile' object has no attribute 'slug')
- before December 31, a backup from November 13, 2024, that fortunately I had kept, worked.
So, I lost 4 months of work. 
Please help fix this malfunction so that I can recover the newest version.
Thanks

### What version of Home Assistant Core has the issue?

core-2025.3.0

### What was the last working version of Home Assistant Core?

core-2024.12.0

### What type of installation are you running?

Home Assistant OS

### Integration causing the issue

Installing the latest version, core 2025.3.0

### Link to integration documentation on our website

_No response_

### Diagnostics information

![Image](https://github.com/user-attachments/assets/b3f44def-058a-49cb-b29d-13d0e64742db)

![Image](https://github.com/user-attachments/assets/0fb6cbb0-a16e-4a61-a84b-b5b75775a450)


### Example YAML snippet

```yaml

```

### Anything in the logs that might be useful for us?

```txt
Nothing special in the logs
```

### Additional information

_No response_


Discussion:
Same problem here. I am trying to restore to a fresh install.
Any solution yet?
Thanks for your quick reply. 
I'm running in "degraded" mode, but it makes the services indispensable to me. I can wait 1-2 weeks if you can fix it within that time frame.


Hey there @home-assistant/core, mind taking a look at this issue as it has been labeled with an integration (`backup`) you are listed as a [code owner](https://github.com/home-assistant/core/blob/dev/CODEOWNERS#L185) for? Thanks!

<details>
<summary>Code owner commands</summary>


Code owners of `backup` can trigger bot actions by commenting:

- `@home-assistant close` Closes the issue.
- `@home-assistant rename Awesome new title` Renames the issue.
- `@home-assistant reopen` Reopen the issue.
- `@home-assistant unassign backup` Removes the current integration label and assignees on the issue, add the integration domain after the command.
- `@home-assistant add-label needs-more-information` Add a label (needs-more-information, problem in dependency, problem in custom component) to the issue.
- `@home-assistant remove-label needs-more-information` Remove a label (needs-more-information, problem in dependency, problem in custom component) on the issue.

</details>


<sub><sup>(message by CodeOwnersMention)</sup></sub>

---

[backup documentation](https://www.home-assistant.io/integrations/backup)
[backup source](https://github.com/home-assistant/core/tree/dev/homeassistant/components/backup)
<sub><sup>(message by IssueLinks)</sup></sub>
Having this issue as well. Brand new HA green and trying to restore a backup to it. 
Same issue.  Trying to restore a HA yellow backup onto a HAOS vm as a migration method.
Core 2025.3.1
Supervisor 2025.03.2
Operating System 14.2
Frontend 20250306.0
I'm having the same issue. I'm trying to restore it to a mini-pc.
So I found a seeming workaround, when I went to restore it, the location showed the file as local and encrypted.  I downloaded it from the new HA VM, to which it then asked me for an encryption key.  I had to give it the key from my old HA machine.  Then I reuploaded it and now when I go to restore it, it shows as "This system - Unencrypted" as the location.  It seems to be restoring now.
By mistake, I upgraded to 2025.3.2 before this, so it's entirely possible it's fixed in the new release, but I don't see it in the release notes anywhere after a quick perusal 
The error code originates from supervisor, are any of you able to share supervisor logs from when this happens?
I restarted a restore from the backup from February 20th, which caused the anomaly.
Here is the log file.
Good reception

![Image](https://github.com/user-attachments/assets/9822bdde-49d2-4f2a-8439-3a2412bbde07)

[home-assistant_2025-03-12T13-59-29.886Z.log](https://github.com/user-attachments/files/19211457/home-assistant_2025-03-12T13-59-29.886Z.log)
And now the supervisor logs :

[Supervisor.log](https://github.com/user-attachments/files/19212206/Supervisor.log)
At the end of Supervisor.log file, there is : 
    f"**Invalid password** for backup {backup.slug}", _LOGGER.error

But HA begins restoration process without asking password !
Thanks for the logs!
The bug has been found, and will be fixed in an upcoming release of Supervisor.
Wow, great, thanks a lot.
I'll be able to recover the hard work I did on my Dashboards.
Please let us know when the release will be available.
First, download the backup file to your PC. Then, go to the HA backup page and upload it either by drag and drop or via the file explorer. That worked for me at least.
Okay to test with my backup, but where can I find the new code to update my HA ?
Is there a core-2025.3.3 available that I could test with my backup, and how do I install it in my HA?
It's 11 PM in France now, and I'll test it tomorrow.
Good evening.



Patch:
diff --git a/supervisor/backups/backup.py b/supervisor/backups/backup.py
index f290189c531..e3a665f8f9d 100644
--- a/supervisor/backups/backup.py
+++ b/supervisor/backups/backup.py
@@ -392,7 +392,7 @@ def _validate_file() -> None:
                         return
                 except tarfile.ReadError as ex:
                     raise BackupInvalidError(
-                        f"Invalid password for backup {backup.slug}", _LOGGER.error
+                        f"Invalid password for backup {self.slug}", _LOGGER.error
                     ) from ex
 
         try:


Test patch:
diff --git a/tests/backups/test_backup.py b/tests/backups/test_backup.py
index 13109c48af6..10e8ca7c089 100644
--- a/tests/backups/test_backup.py
+++ b/tests/backups/test_backup.py
@@ -1,5 +1,6 @@
 """Test backups."""
 
+from contextlib import AbstractContextManager, nullcontext as does_not_raise
 from os import listdir
 from pathlib import Path
 from shutil import copy
@@ -116,28 +117,44 @@ async def test_consolidate(
     }
 
 
-@pytest.mark.asyncio
 @pytest.mark.parametrize(
-    "tarfile_side_effect, securetar_side_effect, expected_exception",
+    (
+        "tarfile_side_effect",
+        "securetar_side_effect",
+        "expected_exception",
+    ),
     [
-        (None, None, None),  # Successful validation
-        (FileNotFoundError, None, BackupFileNotFoundError),  # File not found
-        (None, tarfile.ReadError, BackupInvalidError),  # Invalid password
+        (None, None, does_not_raise()),  # Successful validation
+        (
+            FileNotFoundError,
+            None,
+            pytest.raises(
+                BackupFileNotFoundError,
+                match=r"Cannot validate backup at [^, ]+, file does not exist!",
+            ),
+        ),  # File not found
+        (
+            None,
+            tarfile.ReadError,
+            pytest.raises(
+                BackupInvalidError, match="Invalid password for backup 93b462f8"
+            ),
+        ),  # Invalid password
     ],
 )
 async def test_validate_backup(
     coresys: CoreSys,
     tmp_path: Path,
-    tarfile_side_effect,
-    securetar_side_effect,
-    expected_exception,
+    tarfile_side_effect: type[Exception] | None,
+    securetar_side_effect: type[Exception] | None,
+    expected_exception: AbstractContextManager,
 ):
     """Parameterized test for validate_backup."""
     enc_tar = Path(copy(get_fixture_path("backup_example_enc.tar"), tmp_path))
     enc_backup = Backup(coresys, enc_tar, "test", None)
     await enc_backup.load()
 
-    backup_tar_mock = MagicMock()
+    backup_tar_mock = MagicMock(spec_set=tarfile.TarFile)
     backup_tar_mock.getmembers.return_value = [
         MagicMock(name="test.tar.gz")
     ]  # Fake tar entries
@@ -150,16 +167,14 @@ async def test_validate_backup(
         patch(
             "tarfile.open",
             MagicMock(
-                return_value=backup_context_mock, side_effect=tarfile_side_effect
+                return_value=backup_context_mock,
+                side_effect=tarfile_side_effect,
             ),
         ),
         patch(
             "supervisor.backups.backup.SecureTarFile",
             MagicMock(side_effect=securetar_side_effect),
         ),
+        expected_exception,
     ):
-        if expected_exception:
-            with pytest.raises(expected_exception):
-                await enc_backup.validate_backup(None)
-        else:
-            await enc_backup.validate_backup(None)
+        await enc_backup.validate_backup(None)