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:
@@ -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.
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user