PSA: Why your MP3s disappear on Garmin watches via MTP (and how to actually fix it)

# The Ultimate Guide to Transferring Music to Garmin Watches via MTP on macOS

- **Watch**: Garmin Forerunner 265
- **Computer**: MacBook Air M4 (macOS Sequoia)
- **Tool**: libmtp 1.1.23 (`mtp-sendtr`)

## Who This Is For
- macOS users (Windows users: just use Garmin Express, you'll probably be fine)
- Transferring large local MP3 libraries (50+ tracks)
- Garmin Express is broken, OpenMTP won't accept drag-and-drop, Android File Transfer is discontinued
- You're stuck with `libmtp` command-line tools

## The Problem

`mtp-sendtr` returns `New track ID: xxxxx` — looks successful. But after disconnecting and reconnecting, the songs are gone. Or worse: 2-5x duplicate entries appear in the watch's music library (ghost tracks), even though only one copy of each file exists on disk.

## Root Cause

**Garmin's MTP implementation has a ghost-entry bug**: once a filename has ever appeared on the watch (even after deletion via `mtp-delfile`), that filename is permanently remembered in the watch's internal MTP database. Sending a file with the same name again → the CLI reports success → the file is silently discarded.

Garmin's own documentation acknowledges this indirectly: *"Mac operating systems provide limited support for MTP file transfer mode. You must open the Garmin drive on a Windows operating system."*

## The Fix

### Three Iron Rules
1. **Use short, unique numerical filenames on the watch**: `A001.mp3`, `A002.mp3`... Never reuse a number.
2. **Bare send, no metadata flags**: `mtp-sendtr -q file.mp3 Music/A001.mp3`. Do NOT use `-t -a -l -g` flags — the Garmin watch reads ID3 tags directly from the MP3 file. Passing MTP metadata properties triggers `player does not support this abstract type`.
3. **Numbers are never recycled**: when deleting a song, mark it `deleted` in your index. Skip the number forever.

### Step-by-Step

```bash
# 1. Ensure MP3s have complete ID3 tags (artist, album, genre)
ffprobe your_song.mp3

# 2. Create a numbering index (song_map.json)
# {"A001": {"source": "song1.mp3", "title": "...", "artist": "..."}, ...}

# 3. Send via Python subprocess (avoids shell quoting issues)
python3 -c "
import json, subprocess
m = json.load(open('song_map.json'))
for rid in sorted(m):
subprocess.run(['mtp-sendtr', '-q', m[rid]['source'], f'Music/{rid}.mp3'],
capture_output=True, text=True, timeout=15)
"
```

### If You Already Have Mass Ghost Entries

**The only fix: factory reset the watch.** There is no "rebuild music library" option. Deleting files via MTP does not clear the internal database.

## Key Takeaways
- `New track ID` ≠ file actually persisted. Always verify with `mtp-files`.
- `mtp-sendtr` metadata flags (`-t -a -l -g`) cause `player does not support this abstract type` on Garmin — don't use them.
- Batch size of ~50 per MTP session is safe; larger batches risk session drops.
- Factory reset is the cleanest starting point.

---

*Battle-tested: macOS Sequoia + Forerunner 265 + libmtp 1.1.23, 112 MP3s transferred successfully with correct artist/album/genre categorization.*