v2.13.0 — visible failures, real retries, and the wrong-version fix - #56
Merged
Conversation
Three reported "feature requests" turned out to be something else. Measuring
first is what kept this release small.
Missing Tracks was never empty, it was erroring: /upload asked for per_page=500
against an endpoint capped at 200. The 422 was invisible because api.ts discarded
the response body, so every caller's `catch { setRows([]) }` turned a specific
server error into "you have no missing tracks". The client now keeps the status
and FastAPI's detail, retries only transport faults and 5xx, and the page pages
through all 270 errors.
"Fingerprint mismatch" involved no fingerprints. 108 of 109 came from the
file-index title+artist matcher, which accepted any file whose title and artist
agreed -- extended mixes, radio edits, live takes. Across 265 such matches on the
live library, 155 land within 5s of the Spotify duration and 110 exceed it (95 by
more than 15s), so the populations separate cleanly at 5s. Renamed Wrong Version.
Post-processing was working all along (399/400 recent tracks have cues; 4 of 5,612
lack them) -- there was just no way to see it. Coverage is now on the dashboard,
including an honest note about what Lexicon's API cannot report. One real bug did
surface: "Auto-Analyze After Sync" was silently disabling cue generation, tag
lookup and cloud upload too.
Bulk retry resolves categories server-side with the same classifier that renders
the count, and clears fallback_attempts -- without which a retry was inert, since
already_attempted() treats any prior row as "we tried this". The single-track
retry had the same defect.
Also: Soulseek health surfaced via the worker (sync-api cannot import worker code);
dashboard month drill-down with a sargable filter and the first real indexes on
tracks; the health probe no longer pulls Lexicon's entire library every 10s; and
CI now actually runs the ~20 test suites that nothing was executing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HmUiLHPmKoz215WAWV5eHe
tempfile.mktemp returns a name without creating the file, leaving a window in which another process can create it first (CodeQL py/insecure-temporary-file). The new test copied the convention used throughout the existing suites, which is what made CodeQL flag it as a NEW alert on this PR. Creating a temp directory atomically and owning every path inside it is the same amount of code. The 19 pre-existing instances elsewhere in the repo are untouched here -- they are a separate cleanup. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HmUiLHPmKoz215WAWV5eHe
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three of the reported feature requests turned out to be different problems than described. Measuring them first is what kept this release small.
per_page=500from an endpoint capped at 200, got a 422, and rendered an empty table.Highlights
api.tswas hiding server errors. It threw away the response body and raisedError("API error: 422"), so every caller'scatch { setRows([]) }turned a specific, loud failure into "you have nothing". That is the only reason the Missing Tracks bug survived. The client now preserves status + FastAPI'sdetailand retries only transport faults and 5xx — never a 4xx, which is deterministic.The wrong-version fix. The file-index title+artist matcher accepted any file whose title and artist agreed, so extended mixes, radio edits and live takes all matched; verify then rejected them on duration and parked them as errors. Measured across 265 such matches on the live library: 155 within 5s, 110 beyond it, 95 of those by more than 15s. The populations separate cleanly, so matches now require durations to agree within 5s (configurable, fails open when either is unknown).
Bulk retry that isn't inert.
already_attempted()treats any priorfallback_attemptsrow as "we tried this", so resetting the pipeline columns alone produced a track that walked straight back to the same error without re-contacting Soulseek. Bulk retry clears them; the pre-existing single-track retry had the same defect and is fixed too.One real bug in post-processing: "Auto-Analyze After Sync" silently disabled cue generation, tag lookup and cloud upload as well, despite each having its own checkbox and the toggle's description mentioning only BPM/key.
Also
app_config(sync-api cannot import worker code)monthfilter is a half-open range so it can use an index — andtrackshad no index at all beyond its implicit PK oneGET /v1/tracks(Lexicon's entire library) every 10 seconds; now/v1/playlistsCI
The repo had ~20 test files and no workflow that ran them. There is one now — pytest (not
unittest, which silently collects zero tests from the bare-function suites and reports success), plus a web typecheck and a realnext build, which is what catches auseSearchParams()outside a Suspense boundary.It immediately exposed three latent problems, all fixed here: two test modules fighting over
db.py's module-levelDB_PATH(decided by whichever imports first), and a Tidal test that had been failing for anyone outside the container becausetiddlis installed by the Dockerfile but absent fromrequirements.txt.Verification
sync-api: 37 passedsync-worker: 279 passed, 0 failedsync-web: typecheck clean, all 11 pages build🤖 Generated with Claude Code
https://claude.ai/code/session_01HmUiLHPmKoz215WAWV5eHe