[GH-ISSUE #703] yt.add_history_item() fails silently #457

Closed
opened 2026-02-27 23:00:54 +03:00 by kerem · 8 comments
Owner

Originally created by @Batwam on GitHub (Dec 26, 2024).
Original GitHub issue: https://github.com/sigma67/ytmusicapi/issues/703

Describe the bug

I have a script which runs automatically in the background and sends my play history to yt music using ytmusicapi (to improve the quality of my music recommendations). I recently lost authentication (solved now) and being set up to check for 204 response, my script didn't return any error, so, it took me 5 days to realise that it was broken.

Turns out that yt.add_history_item() appears to return Response [204] in cases the request fails. In fact, I don't think that I ever got anything else than 204. This might be due to YT Music return success status regardless of the query but it would be good to flag this to the user.

My solution currently is to wait a few seconds and check the videoId of the last history item to make sure it is matching but ideally the Response would be sufficient or there would be some built-in check to at least make sure add_history_item() is done through an authenticated session (like what is done when adding items to playlists I believe).

As an added secondary observation/bug I noticed that the history is only updated if get_song() is obtained also using an authenticated session. I'm not sure why this is the case as I would have thought that it's the same song regardless. Just putting it out there too (see test case #2)

To Reproduce
Run this test script:

  • case 1 silently fails: anonymous add_history_item request: should fail but returns 204
  • case 2 silently fails: anonymous get_song() search followed by authenticated request. Should (probably) work, returns 204 but doesn't actually update the YT Music history. Seems like get_song() returns a different/incorrect/invalid value if non-authenticated?
  • case 3 succeeds: authenticated search and add_item actually update the history
import time                                                   
from ytmusicapi import YTMusic                                
                                       
                                                              
yt_anonymous = YTMusic()                                      
yt = YTMusic("browser.json")                 
#"title":"Steve Berman","author_name":"Eminem - Topic"        
videoId="pRoQwMTWv6M"                                         
print("videoId: "+videoId)                                    
                                                              
search_results = yt_anonymous.get_song(videoId)               
result=yt_anonymous.add_history_item(search_results)          
print("\ntest1 result: "+str(result))                         
history=yt.get_history()                                      
time.sleep(5)                                                 
print("last history videoId: "+str(history[0]['videoId']))    
                                                              
#add to history while authenticated                           
search_results = yt_anonymous.get_song(videoId)               
result2=yt.add_history_item(search_results)                   
print("\ntest2 result: "+str(result2))                        
history=yt.get_history()                                      
time.sleep(5)                                                 
print("last history videoId: "+str(history[0]['videoId']))    
                                                              
#search and add to history while authenticated                
search_results3 = yt.get_song(videoId)                        
result3=yt.add_history_item(search_results3)                  
print("\ntest3 result: "+str(result3))                        
time.sleep(5)                               
history=yt.get_history()                                      
print("last history videoId: "+str(history[0]['videoId']))

Script Result


videoId: pRoQwMTWv6M

test1 result: <Response [204]>
last history videoId: L50aIoPvMoY

test2 result: <Response [204]>
last history videoId: L50aIoPvMoY

test3 result: <Response [204]>
last history videoId: pRoQwMTWv6M
Originally created by @Batwam on GitHub (Dec 26, 2024). Original GitHub issue: https://github.com/sigma67/ytmusicapi/issues/703 **Describe the bug** I have a script which runs automatically in the background and sends my play history to yt music using ytmusicapi (to improve the quality of my music recommendations). I recently lost authentication (solved now) and being set up to check for 204 response, my script didn't return any error, so, it took me 5 days to realise that it was broken. Turns out that yt.add_history_item() appears to return Response [204] in cases the request fails. In fact, I don't think that I ever got anything else than 204. This might be due to YT Music return success status regardless of the query but it would be good to flag this to the user. My solution currently is to wait a few seconds and check the videoId of the last history item to make sure it is matching but ideally the Response would be sufficient or there would be some built-in check to at least make sure add_history_item() is done through an authenticated session (like what is done when adding items to playlists I believe). As an added secondary observation/bug I noticed that the history is only updated if get_song() is obtained also using an authenticated session. I'm not sure why this is the case as I would have thought that it's the same song regardless. Just putting it out there too (see test case #2) **To Reproduce** Run this test script: - case 1 silently fails: anonymous add_history_item request: should fail but returns 204 - case 2 silently fails: anonymous get_song() search followed by authenticated request. Should (probably) work, returns 204 but doesn't actually update the YT Music history. Seems like get_song() returns a different/incorrect/invalid value if non-authenticated? - case 3 succeeds: authenticated search and add_item actually update the history ``` import time from ytmusicapi import YTMusic yt_anonymous = YTMusic() yt = YTMusic("browser.json") #"title":"Steve Berman","author_name":"Eminem - Topic" videoId="pRoQwMTWv6M" print("videoId: "+videoId) search_results = yt_anonymous.get_song(videoId) result=yt_anonymous.add_history_item(search_results) print("\ntest1 result: "+str(result)) history=yt.get_history() time.sleep(5) print("last history videoId: "+str(history[0]['videoId'])) #add to history while authenticated search_results = yt_anonymous.get_song(videoId) result2=yt.add_history_item(search_results) print("\ntest2 result: "+str(result2)) history=yt.get_history() time.sleep(5) print("last history videoId: "+str(history[0]['videoId'])) #search and add to history while authenticated search_results3 = yt.get_song(videoId) result3=yt.add_history_item(search_results3) print("\ntest3 result: "+str(result3)) time.sleep(5) history=yt.get_history() print("last history videoId: "+str(history[0]['videoId'])) ``` **Script Result** ``` videoId: pRoQwMTWv6M test1 result: <Response [204]> last history videoId: L50aIoPvMoY test2 result: <Response [204]> last history videoId: L50aIoPvMoY test3 result: <Response [204]> last history videoId: pRoQwMTWv6M ```
kerem closed this issue 2026-02-27 23:00:54 +03:00
Author
Owner

@Batwam commented on GitHub (Dec 27, 2024):

I compared the anonymous and authenticated results for get_song() and they are indeed different in quite a few areas. I'm not sure why but either way, the non-authenticated result doesn't work when trying to use it with add_history_item so, that's good to know I guess.

<!-- gh-comment-id:2563305608 --> @Batwam commented on GitHub (Dec 27, 2024): I compared the anonymous and authenticated results for get_song() and they are indeed different in quite a few areas. I'm not sure why but either way, the non-authenticated result doesn't work when trying to use it with add_history_item so, that's good to know I guess.
Author
Owner

@ImpenetrableNoble commented on GitHub (Dec 27, 2024):

@Batwam The issue with yt.add_history_item() always returns a 204 response sometimes so I can agree, even when the request fails, it can be addressed by manually verifying if the item was added to the history by fetching the last history item and comparing its videoId, or by explicitly checking if the session is authenticated before making the call, and if the library lacks proper error handling, consider wrapping the call in a try-except block or raising a feature request for improved response handling.

Especially the observation that history only updates when get_song() is called with an authenticated session suggests that YouTube Music tracks user-specific metadata differently when authenticated, so ensure get_song() is always called with an authenticated session, and if this behavior is unintuitive, consider raising an issue with the library maintainers for clarification or fixes.

Try it out yourself, until the developer update a fix for it.

<!-- gh-comment-id:2563373570 --> @ImpenetrableNoble commented on GitHub (Dec 27, 2024): @Batwam The issue with `yt.add_history_item()` always returns a `204` response sometimes so I can agree, even when the request fails, it can be addressed by manually verifying if the item was added to the history by fetching the last history item and comparing its `videoId`, or by explicitly checking if the session is authenticated before making the call, and if the library lacks proper error handling, consider wrapping the call in a try-except block or raising a feature request for improved response handling. Especially the observation that history only updates when `get_song()` is called with an authenticated session suggests that YouTube Music tracks user-specific metadata differently when authenticated, so ensure `get_song()` is always called with an authenticated session, and if this behavior is unintuitive, consider raising an issue with the library maintainers for clarification or fixes. Try it out yourself, until the developer update a fix for it.
Author
Owner

@Batwam commented on GitHub (Dec 27, 2024):

Yeah, I already successfully implemented all these checks, that's why I mentioned them above and also included the 5s delay before checking the history (without the delay, it doesn't have sufficient time to update). I'm also now checking authentication by making sure there is a handle by using get_info().

My aim here is mostly to share/flag this in case we believe that it should get picked up automatically to save other people having to do the troubleshooting I just did, especially since this doesn't appear to be captured in the documentation

For instance, I'm not sure at what level it's getting picked up but I'm pretty sure that you'd get an error if you tried to submit a playlist item without being authenticated.

<!-- gh-comment-id:2563386361 --> @Batwam commented on GitHub (Dec 27, 2024): Yeah, I already successfully implemented all these checks, that's why I mentioned them above and also included the 5s delay before checking the history (without the delay, it doesn't have sufficient time to update). I'm also now checking authentication by making sure there is a handle by using get_info(). My aim here is mostly to share/flag this in case we believe that it should get picked up automatically to save other people having to do the troubleshooting I just did, especially since this doesn't appear to be captured in the [documentation](https://ytmusicapi.readthedocs.io/en/stable/reference.html#ytmusicapi.YTMusic.add_history_item) For instance, I'm not sure at what level it's getting picked up but I'm pretty sure that you'd get an error if you tried to submit a playlist item without being authenticated.
Author
Owner

@ImpenetrableNoble commented on GitHub (Dec 27, 2024):

@Batwam The documentation should include a note about the authentication requirement and suggest using get_info() or similar methods to verify authentication beforehand.

Otherwise, the library will just seem unreliable or buggy.

The library should be updated to explicitly check for authentication before attempting to add a history item and raise a clear error if any user isn’t authenticated.

<!-- gh-comment-id:2563398332 --> @ImpenetrableNoble commented on GitHub (Dec 27, 2024): @Batwam The documentation should include a note about the authentication requirement and suggest using `get_info()` or similar methods to verify authentication beforehand. Otherwise, the library will just seem unreliable or buggy. The library should be updated to explicitly check for authentication before attempting to add a history item and raise a clear error if any user isn’t authenticated.
Author
Owner

@sigma67 commented on GitHub (Dec 27, 2024):

@Batwam this is not really a bug. I don't know why you'd use different sessions for both calls. I agree that the guard check_auth should be added to add_history_item. PR welcome

@ImpenetrableNoble get out of here with your "AI" generated nonsense

<!-- gh-comment-id:2563878319 --> @sigma67 commented on GitHub (Dec 27, 2024): @Batwam this is not really a bug. I don't know why you'd use different sessions for both calls. I agree that the guard `check_auth` should be added to `add_history_item`. PR welcome @ImpenetrableNoble get out of here with your "AI" generated nonsense
Author
Owner

@Batwam commented on GitHub (Dec 27, 2024):

@sigma67 The reason for the unauthenticated search isn't obvious in this short example but came up in my scrobbling script code and this is something I was going to raise separately.

Basically to do my yt scrobbling, I'm pulling the history from LastFM, doing a yt.search(song) to figure out the videoId and then a search get_song(videoId), then passing the result to add_playlist_item. The problem I found with doing an authorised search song is that it fills my search bar with all the searches made by the script.

I then realised that the search doesn't need to be authenticated so I started doing unauthenticated search() which did what I wanted (gets me the same result but doesn't fill my search history)and get_song(), followed by an authenticated add_history_item. This gave me response 204 but actually didn't update the history (case 2 above). That's when I realised that I also had to authenticated get_song() which is why I shared case 2 above.

Essentially the unauthenticated search is so it doesn't fill the yt music search history. It's not a massive issue but I would ideally suggest to either make this unauthenticated search an option within search() or perhaps make the search unauthenticated by default as the results are the same but it doesn't "pollute" the search history.

It works fine the way I'm doing it with the 2 instances so that's doesn't bother me but let me know if you see any value in this idea and I can document it separately as a feature request.

<!-- gh-comment-id:2564090304 --> @Batwam commented on GitHub (Dec 27, 2024): @sigma67 The reason for the unauthenticated search isn't obvious in this short example but came up in my scrobbling script code and this is something I was going to raise separately. Basically to do my yt scrobbling, I'm pulling the history from LastFM, doing a `yt.search(song)` to figure out the videoId and then a search `get_song(videoId)`, then passing the result to add_playlist_item. The problem I found with doing an authorised search song is that it fills my search bar with all the searches made by the script. I then realised that the search doesn't need to be authenticated so I started doing unauthenticated `search()` which did what I wanted (gets me the same result but doesn't fill my search history)and `get_song()`, followed by an authenticated `add_history_item`. This gave me response 204 but actually didn't update the history (case 2 above). That's when I realised that I also had to authenticated get_song() which is why I shared case 2 above. Essentially the unauthenticated search is so it doesn't fill the yt music search history. It's not a massive issue but I would ideally suggest to either make this unauthenticated search an option within search() or perhaps make the search unauthenticated by default as the results are the same but it doesn't "pollute" the search history. It works fine the way I'm doing it with the 2 instances so that's doesn't bother me but let me know if you see any value in this idea and I can document it separately as a feature request.
Author
Owner

@sigma67 commented on GitHub (Dec 28, 2024):

make this unauthenticated search an option within search() or perhaps make the search unauthenticated by default as the results are the same but it doesn't "pollute" the search history.

That's not really an option. It makes sense for your use case but the solution with two instances is sufficient. It would be confusing to anyone else.

It would be a good idea to document the fact that search has the side effect of adding history items in the search documentation. You can always remove them again with remove_history_items

<!-- gh-comment-id:2564245534 --> @sigma67 commented on GitHub (Dec 28, 2024): > make this unauthenticated search an option within search() or perhaps make the search unauthenticated by default as the results are the same but it doesn't "pollute" the search history. That's not really an option. It makes sense for your use case but the solution with two instances is sufficient. It would be confusing to anyone else. It would be a good idea to document the fact that `search` has the side effect of adding history items in the `search` documentation. You can always remove them again with `remove_history_items`
Author
Owner

@Batwam commented on GitHub (Dec 28, 2024):

No problem, as you said, it's relatively easy to work around but probably worth documenting.

<!-- gh-comment-id:2564264823 --> @Batwam commented on GitHub (Dec 28, 2024): No problem, as you said, it's relatively easy to work around but probably worth documenting.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/ytmusicapi#457
No description provided.