-
v2026.08.07 — Minstrel notices when a file is gone, follows it when it moves, and stops overstating what it knows
Stabletest-web / test (push) Successful in 52stest-go / test (push) Successful in 1m10stest-go / integration (push) Successful in 4m53srelease / Build signed APK (tag releases only) (push) Successful in 3m57srelease / Build + push container image (push) Successful in 1m37sreleased this
2026-08-06 22:20:24 -04:00 | 0 commits to main since this releasePer-day CalVer. Server + web, with schema migration 0055. Merged via PR #121 and #122;
mainatd5aa0811, CI green across the Go lane (incl. the real-migration integration suite), the web lane, and the image build.This tag was re-cut. An earlier
v2026.08.07covered the missing-file work alone ata99f855e. Rather than mint a second dated tag for the same day, the tag and its release were replaced so today has one release covering both changes. The superseded build remains addressable by its commit-SHA image (:a99f855e) if you ever need it.Two threads. The first began as a bug report about genre names and ended somewhere else entirely; the second is about a screen that was quietly overstating its own confidence.
Some of your library doesn't exist
Yesterday's release fixed how genres are read. You redeployed, and still saw nonsense genres in the list. That turned out to be a completely different bug, and a considerably worse one.
You reported entries like
Alternative RockAlternative MetalRockRap MetalPop Rock…surviving a completed scan. The scan log saidscanned=24185 skipped=24185 errored=0— everything checked, nothing wrong.Meanwhile, a different stage of the same scan was quietly logging this, about forty times:
track mbid backfill: open failed path=".../Linkin Park - Minutes to Midnight - 07 - Hands Held High.mp3" err="no such file or directory"Both statements were true. The library scanner walks your folders, so it only ever sees files that exist. A database row whose file has been deleted or moved is never visited — never scanned, never counted, never reported as an error. It is simply invisible to the scan. The MBID stage opens files by their recorded path instead of walking, which is why it tripped over them and the scanner never did.
Those rows had been sitting there since whenever those albums were last reorganised, and nothing in Minstrel has ever removed one. The genre fix couldn't reach them for exactly the same reason: it can only re-read files the walk visits.
Why it mattered more than a messy list
A row like that is a live library entry. Nothing downstream knew the file was gone, and the track table is the pool every recommendation draws from — so Discover, For You, Deep Cuts, radio, all of it could hand you a track that cannot play. That's a mix quietly coming up short, or playback stopping for no visible reason.
It now notices — and deliberately doesn't delete anything
Minstrel records that a file is missing rather than removing the row, and stops offering it. Missing tracks no longer appear in genre browse, and no longer get picked for any mix, radio station or playlist.
Deleting felt like the obvious answer and is the wrong one, for two separate reasons.
The filesystem lies. "This file isn't there" is a claim that can be temporarily false — an unmounted drive, a network share that hiccupped, a container that started before its media volume attached. Every other cleanup job in Minstrel answers a question the database can settle by itself, which is safe to do unattended. This one can't. So it marks, which is reversible: when the file comes back, the mark clears on its own.
Three guards refuse to guess:
- every configured library folder must exist and contain something — an empty folder is what a mount point looks like with nothing mounted on it
- the scan must have found at least one file
- at most a quarter of your library can be newly marked in one pass
Recovery deliberately ignores that last limit, so a library that hit it once can still un-mark everything when the drive returns.
And deleting would damage your own history. This is the part worth knowing: your taste profile reads your plays through the track, so a marked-but-kept row keeps contributing what you learned from it. Deleting the row takes its play history and its like with it. That's the thing that would actually distort your recommendations — so keeping it is the accurate choice, not the timid one.
What stays untouched, on purpose: your listening history and your own playlists. A track you added yourself doesn't vanish because a file moved, and history is a record of what happened. Only the "what should we play next" machinery filters them out.
A renamed song no longer loses its past
You raised this yourself, and it turned out to be the more important half.
Minstrel identified a track by its file path. So renaming a file — retagging an album, fixing track numbers, reorganising folders — looked like one song being deleted and a different song appearing. The consequences were silent:
- a song you'd liked came back unliked
- its play count reset to zero
- Rediscover could offer you something you'd played fifty times, as a discovery
Renumbering a single album was enough to do it. Your copy of Minutes to Midnight is the case that proved it: the database still held
02 - Bleed It Outwhile the file on disk is now04 - Bleed It Out.Minstrel now recognises a returning file as the same recording and reconnects it to its existing entry, keeping its likes, plays and playlist spots. It matches on the MusicBrainz recording ID where your files have one, and falls back to exact file size plus exact duration where they don't.
It is careful about it. It will only ever reconnect a file to an entry it already believes is missing — anything else is a duplicate, and adopting one of those would break the copy that still exists. And if two entries match equally well, it refuses to choose and starts a fresh one instead: a split history can be repaired later, whereas two songs wrongly merged cannot.
The bug that nearly shipped in this feature
Worth recording because it's the kind that hides. Reconnecting requires knowing a file went missing before the new one is examined — and a rename made while the server is off produces both facts in the same scan. In the first version, the new file got filed as a brand-new song moments before the old one was marked missing, and after that the chance was gone forever.
Which means it would have worked perfectly for renames made while Minstrel was running, and silently failed for exactly the case that produced your problem. The scan is now split so it takes stock of what's gone before deciding what's new.
The tuning numbers now admit what they don't know
Different kind of fix. Nothing was broken here — the screen was just more confident than the arithmetic warranted, and it talked me into a wrong recommendation.
Settings → Recommendation metrics compares each surface against your own manual picks. Discover's taste-matched arm showed a 13-point better skip rate than its random-unheard arm, rendered as a bold coloured number like any other. It looks like a finding. It isn't: at 59 and 70 plays that gap is well inside its own noise, and you'd need roughly 133 plays per arm before it could be called.
The card had one volume threshold, and it was doing two jobs. Twenty plays is enough to be worth showing — below that a rate is anecdote. It is nowhere near enough to act on. Those are different questions and the screen only answered the first.
Every delta now carries its own margin of error. When a difference is smaller than that margin it renders dimmed with a
≈and no colour, and hovering gives the range. So a−6drawn from 400 plays and a−12drawn from 59 no longer look alike — because the second one might be nothing.There's a hint of it in the copy too: the exact comparison above is now a test that fails if the false confidence ever comes back.
Admin → Tuning had the same problem, worse. Its skip and completion columns are the most recent week while the Plays column beside them covers the whole window — so a rate off seventeen plays sat next to a four-figure total and read as solid. I misjudged Deep cuts on exactly that basis; over 180 days it's one of your better surfaces. The columns now say which period they cover, and the skip rate carries that week's play count.
Also in this release
Scans no longer log a recurring database
ERRORfor a condition that is entirely expected — two artist rows mapping to the same MusicBrainz artist, which means they're candidates to be merged, not a fault. The album side had always treated it that way; the artist side hadn't.
Install
Redeploy the server image. That's the whole install — migration 0055 applies automatically on restart. No new APK; the Android app is unchanged.
Then let a scan run, or trigger one from Admin → Library.
What to look at afterwards
- Library → Genres. The remaining welded entries should finally disappear — they were coming from those phantom rows.
- The scan record now reports how many tracks were marked missing and how many came back.
- The server log names each reconnection with both paths, so a reorganisation is visible as "track moved, history preserved" rather than passing unnoticed.
- Give it a while, then check a renamed favourite. Is it still liked? Does it still show its play count?
- Settings → Recommendation metrics. Expect to see more
≈than you might like. That's the honest state of a household-sized dataset, not a regression.
Known and deliberate
Nothing is ever deleted, so those rows stay. They're inert — excluded from everything that picks music — but they're still in the database, and there is no screen for reviewing them yet. That's the next piece of work, and it's deliberately separate: it's the only part that can destroy something, and it deserves to be built on its own rather than bundled in here.
Reorganising a very large share of your library at once trips the quarter-of-the-library guard. Nothing breaks, and it's logged plainly, but reconnection can't run that time and those files will start fresh histories. At your library's size this needs thousands of files moved in one go.
Reconnection needs something to recognise. A file that was re-encoded — not just renamed — changes both its size and its duration, and if it also lacks a MusicBrainz ID there's nothing left to match on. That's genuinely a different file, so a fresh entry is the honest outcome.
The margins don't correct for how many comparisons you're scanning. Looking at twenty surfaces and arms at once means the most striking-looking gap is likely to be striking by chance. The per-delta margin catches most of that; the rest is a judgement call, and the honest posture is to distrust the single best-looking number on the page.
🤖 Generated with Claude Code
Downloads