From 7252d4aad30cdc15b520b43648a4953324434876 Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Sun, 19 Jul 2026 10:36:37 -0400 Subject: [PATCH] Fix try_smart_import() silently failing every ManualImport it fires MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The candidate objects from /manualimport only nest ids under .series.id and .movie.id, but Sonarr/Radarr's ManualImport command requires them flattened to top-level seriesId/episodeIds or movieId — every call was failing instantly with "Series/Movie with ID 0 does not exist" while the caller only checked for HTTP 201 (accepted), so every "smart-imported" this script has ever logged was actually a silent no-op. Now flattens the ids before submitting and briefly polls the command afterward so a fast failure falls through to the normal blocklist+research path instead of being reported as success. --- Arrs_Stack/arrs_failed_stalled_recovery.sh | 37 +++++++++++++++++++--- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/Arrs_Stack/arrs_failed_stalled_recovery.sh b/Arrs_Stack/arrs_failed_stalled_recovery.sh index be06d29..8655e2c 100755 --- a/Arrs_Stack/arrs_failed_stalled_recovery.sh +++ b/Arrs_Stack/arrs_failed_stalled_recovery.sh @@ -378,6 +378,14 @@ try_smart_import() { existing_res=$(echo "$existing" | jq -r '.episodeFile.quality.quality.resolution // 0' 2>/dev/null) existing_lang=$(echo "$existing" | jq -r '.episodeFile.languages[0].name // "Unknown"' 2>/dev/null) fi + # /manualimport only nests the IDs under .series.id / .episodes[].id — + # the ManualImport command body needs them flattened to top-level + # seriesId/episodeIds or Sonarr rejects the whole command with + # "Series with ID 0 does not exist" (confirmed live 2026-07-19: every + # smart-import this run reported as successful had actually failed + # this way, silently, since the caller only checks the HTTP 201 accept). + entry=$(echo "$entry" | jq -c --argjson eid "$target_id" \ + '. + {seriesId: .series.id, episodeIds: [$eid]}' 2>/dev/null) ;; radarr) target_id=$(echo "$entry" | jq -r '.movie.id // empty' 2>/dev/null) @@ -391,10 +399,14 @@ try_smart_import() { existing_res=$(echo "$existing" | jq -r '.quality.quality.resolution // 0' 2>/dev/null) existing_lang=$(echo "$existing" | jq -r '.languages[0].name // "Unknown"' 2>/dev/null) fi + # Same flattening issue as Sonarr above — command body needs a + # top-level movieId or Radarr rejects it with "Movie with ID 0 + # does not exist". + entry=$(echo "$entry" | jq -c --argjson mid "$target_id" '. + {movieId: $mid}' 2>/dev/null) ;; esac - [[ -z "$target_id" ]] && return 1 + [[ -z "$target_id" || -z "$entry" ]] && return 1 if [[ "$has_file" != "true" ]]; then decision="import" # nothing there yet — fills a real gap @@ -419,19 +431,36 @@ try_smart_import() { return 0 fi - local files_json cmd_body http_code + local files_json cmd_body response http_code cmd_id cmd_status files_json=$(printf '%s\n' "${qualifying_files[@]}" | jq -s -c '.' 2>/dev/null) [[ -z "$files_json" ]] && return 1 cmd_body=$(jq -c -n --argjson files "$files_json" \ '{name:"ManualImport", files:$files, importMode:"auto"}' 2>/dev/null) [[ -z "$cmd_body" ]] && return 1 - http_code=$(curl -s -o /dev/null -w '%{http_code}' -X POST \ + response=$(curl -s -w '\n%{http_code}' -X POST \ -H "X-Api-Key: $api_key" -H "Content-Type: application/json" \ -d "$cmd_body" \ "${url}/api/${api_version}/command" 2>/dev/null) + http_code=$(echo "$response" | tail -1) + cmd_id=$(echo "$response" | head -n -1 | jq -r '.id // empty' 2>/dev/null) - [[ "$http_code" == "201" ]] + [[ "$http_code" != "201" || -z "$cmd_id" ]] && return 1 + + # A bad payload (e.g. the seriesId/movieId=0 bug this was written to catch) + # fails in ~10ms — well before any real file copy would even start — so a + # brief poll here catches that failure class without racing a genuinely + # long-running import, which is the reason this doesn't poll to completion. + local _i + for _i in 1 2 3; do + sleep 1 + cmd_status=$(curl -sf --max-time 10 -H "X-Api-Key: $api_key" \ + "${url}/api/${api_version}/command/${cmd_id}" 2>/dev/null | jq -r '.status // empty') + [[ "$cmd_status" == "failed" ]] && return 1 + [[ "$cmd_status" == "completed" ]] && break + done + + return 0 } # ==============================================================================================