Skip to content

[Bug]: VERIFY_MEDIA redownload fails on dangling dedup symlinks (Errno 17 + Errno 2) #115

@raphaelbahat

Description

@raphaelbahat

Version

7.4.2 (/app/src/__init__.py in container)

Deployment Method

Docker Compose

Database Type

PostgreSQL

Storage Setup

  • Media on Docker volume using JuiceFS (S3 object storage backend)
  • DEDUPLICATE_MEDIA=true
  • VERIFY_MEDIA=true

Bug Description

VERIFY_MEDIA re-download repeatedly fails for a subset of media with this recurring pattern:

WARNING - Symlink failed, using direct path: [Errno 17] File exists: '../_shared/<name>' -> '/data/backups/media/<chat_id>/<name>'
ERROR   - Error downloading media: [Errno 2] No such file or directory: '/data/backups/media/_shared/<name>'
WARNING - Failed to re-download media for message <id>

This happens after backup completion during media verification/redownload.

What I observed on disk

For failed entries:

  • Chat path exists as a dangling symlink:
    • /data/backups/media/-1002443412147/5956498299518196004_yCSXQMIrYQImig -> ../_shared/5956498299518196004_yCSXQMIrYQImig
  • But _shared has only variant files with extensions/suffixes, e.g.:
    • 5956498299518196004_yCSXQMIrYQImig.mp4
    • 5956498299518196004_yCSXQMIrYQImig (1).mp4
    • 5956498299518196004_yCSXQMIrYQImig (2).mp4
  • Exact _shared/<name> (without extension) is missing.

Current state in this environment:

  • 33 dangling symlinks under media/-1002443412147
  • All 33 have extension/suffixed candidates in _shared

Why this seems to happen (code-path analysis)

In src/telegram_backup.py:

  1. _process_media checks if not os.path.exists(file_path) (around line 1342). For a dangling symlink this is True.
  2. It attempts os.symlink(..., file_path) (line ~1362), which raises Errno 17 because the path entry already exists (even though dangling).
  3. The fallback handles all OSError by doing shutil.move(shared_file_path, file_path) (line ~1368).
  4. If Telethon saved to a different final filename (e.g. extension added / conflict suffix), shared_file_path no longer exists, causing Errno 2.

Also in _verify_and_redownload_media, cleanup uses os.path.exists(file_path) before os.remove (line ~584), so dangling symlinks are not removed before re-download.

Related issue

Likely related symptom family to #30, but this report includes a reproducible verification/redownload path and probable root cause.

Expected Behavior

VERIFY_MEDIA should repair missing/corrupt media without entering repeated Errno 17 + Errno 2 loops.

Suggested Fix (proposal)

  1. Use os.path.lexists when deciding whether file_path already exists and before cleanup/removal in verify flow.
    • If lexists(file_path) and path is dangling symlink, unlink it before creating new symlink.
  2. Treat EEXIST separately from symlink-unsupported errors:
    • for EEXIST, remove/recreate link if dangling;
    • do not immediately fall back to shutil.move.
  3. Capture and use the actual return value of await self.client.download_media(...) as canonical downloaded path (Telethon may append extension or collision suffix).
  4. Ensure DB file_name/file_path is updated to the canonical final filename/path actually written on disk.

If useful, I can open a PR with a concrete patch implementing the above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions