0.6.1: Fix Spotify OAuth scope missing user-library-read

connect.py's OAuth scope request (playlist-read-private
playlist-read-collaborative) didn't match what sldl's own built-in login
flow requests (same two, plus user-library-read). The narrower scope alembic
granted let sldl refresh a valid access token, but Spotify then returned 403
Forbidden loading the playlist regardless of ownership, which sldl turns
into an unhandled-exception crash (exit 134) instead of a clean scope error.
Found by reproducing a friend's crash: his playlist was his own, connected
via his own OAuth, correct redirect URI -- ruling out the ownership/
visibility explanation and pointing at the scope mismatch instead.

Also:
- run-playlist.sh: detect exit 134 specifically and log a pointed hint
  (distinguishing the Forbidden-loading-playlist case from any other
  unhandled sldl exception) instead of just "sldl finished with exit code
  134" with no context.
- README: document that anyone who connected Spotify before this version
  needs to click Connect Spotify again -- existing refresh tokens carry the
  old, narrower scope and don't upgrade themselves.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
andrew
2026-07-15 08:07:30 -06:00
parent 29d6249e88
commit 9976c430aa
3 changed files with 31 additions and 1 deletions
+10
View File
@@ -213,6 +213,8 @@ That's it, done once. alembic stores the resulting OAuth refresh token (encrypte
If you happen to be running a Spotify app created *before* the 2025/2026 change, plain Client ID/Secret still works and this step is optional — but connecting is harmless either way, so there's no reason not to.
> **If you connected Spotify before version 0.6.1:** click **Connect Spotify** again. Versions before 0.6.1 requested a narrower OAuth scope than sldl actually needs, which could get you a token that refreshes fine but then gets 403 Forbidden loading a playlist (see Troubleshooting). Reconnecting grants the correct scope; your old connection doesn't upgrade itself.
## Adding your first playlist
1. Go to **Playlists****Add playlist**.
@@ -288,6 +290,14 @@ Your Spotify app can't read the playlist with app-only (client-credentials) acce
The fix: go to **Settings → Credentials** and click **Connect Spotify** to complete the OAuth login (see "Connecting your Spotify account (OAuth)" above). This mints a user token that works regardless of when your app was created. If you haven't already, add the redirect URI `https://<your-host>/connect/spotify/callback` in your Spotify app's dashboard first, or the connect step itself will fail with a redirect_uri mismatch.
**A playlist's job log shows `sldl crashed (exit 134)` mentioning a 403 Forbidden, even though you've connected Spotify.**
Two possible causes, in order of likelihood:
1. **You connected before version 0.6.1.** Earlier versions requested a narrower OAuth scope than sldl actually needs, so the token refreshes fine but then gets 403 loading a playlist regardless of ownership. Fix: go to **Settings → Credentials** and click **Connect Spotify** again to grant the correct scope.
2. **The connected account genuinely can't see this playlist.** Once connected, playlist reads are scoped to what that account can actually see — its own playlists, ones it collaborates on, or ones marked Public — not any arbitrary playlist by ID the way app-only access could. Fix: make sure the playlist is owned by the connected account, or set it to Public on Spotify (Playlist → ⋯ → Make public).
Either way, re-run the playlist after fixing it.
**Dedup found something that isn't actually a duplicate.**
Use the "Keep both" button on that pair. alembic will remember your decision and won't flag that exact pair again.
+7 -1
View File
@@ -25,7 +25,13 @@ router = APIRouter(tags=["spotify-connect"])
AUTHORIZE_URL = "https://accounts.spotify.com/authorize"
TOKEN_URL = "https://accounts.spotify.com/api/token"
SCOPES = "playlist-read-private playlist-read-collaborative"
# Matches exactly what sldl's own built-in OAuth login flow requests (seen in
# its printed authorize URL: user-library-read + these two) -- granting a
# narrower scope than sldl expects gets a token sldl can refresh fine but
# then gets 403 Forbidden from Spotify partway through loading a playlist,
# which sldl turns into an unhandled-exception crash (exit 134) rather than a
# clean scope error.
SCOPES = "playlist-read-private playlist-read-collaborative user-library-read"
def _callback_redirect_uri(request: Request) -> str:
+14
View File
@@ -79,6 +79,20 @@ timeout --kill-after=30s "$SLDL_TIMEOUT" \
|| SLDL_EXIT=$?
if [[ "$SLDL_EXIT" -eq 124 || "$SLDL_EXIT" -eq 137 ]]; then
log "ERROR: sldl exceeded ${SLDL_TIMEOUT}s and was killed (likely a hang)"
elif [[ "$SLDL_EXIT" -eq 134 ]]; then
# sldl aborts (SIGABRT) on ANY unhandled .NET exception rather than failing
# cleanly. The one case we've actually seen in the wild: a valid, freshly-
# refreshed user token still gets 403 Forbidden loading the playlist,
# because Spotify scopes OAuth playlist reads to what the CONNECTED account
# can see (its own playlists, ones it collaborates on, or ones marked
# Public) -- unlike client-credentials, which could read any public
# playlist regardless of whose app it was. Give a pointed hint when that's
# the visible cause; otherwise just flag that sldl crashed outright.
if grep -q "APIException: Forbidden" "$LOG"; then
log "ERROR: sldl crashed (exit 134) -- Spotify returned 403 loading this playlist. The connected Spotify account can't see it: make sure it's owned by, or shared/followed by, whichever account is connected via /connect/spotify, or set it to Public on Spotify."
else
log "ERROR: sldl crashed (exit 134, unhandled exception) -- see the log above for the exception detail"
fi
fi
log "sldl finished with exit code $SLDL_EXIT"