You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
SummaryTwo separate bugs in the remote-task delivery pipeline (unmanic/libs/installation_link.py / unmanic/libs/postprocessor.py) can silently replace a good source file with a tiny (~55 byte) truncated stub, destroying the original media with no error surfaced to the user. Found via unmanic.db history logs and confirmed by re-checking on-disk file sizes after "successful" tasks.Unmanic version: 0.4.0+e838573 (remote installation) / 0.4.1~1c324b8 (main installation)Plugin: video_transcoder (Transcode Video Files)## Bug 1: "Reject File if Larger than Original" reset reads from an expired staging pathWhen a remote-worker transcode produces output larger than the source, the reject_files_larger_than_original plugin resets the task back to the original file. On long-running remote tasks (60-90+ min, e.g. an overloaded/slow hardware encoder), the "original" it resets to is a temp staging copy under unmanic_remote_pending_library-<random> — created for the remote handoff — which appears to no longer be intact/available by the time the reset runs. The result written back to the library is a few dozen bytes instead of the real source.Example from our logs (paths redacted):Resetting task file back to original source as current cache file is larger than the original file: - Original File: 463218706 bytes '.../unmanic_remote_pending_library-<redacted>/<file>.mkv' - Cache File: 1137643964 bytes '.../unmanic_file_conversion-<redacted>/<file>-WORKING-2-1.mkv'Runner did not request for Unmanic to execute a commandTask was marked task_success: true. On-disk file after this task: 55 bytes.## Bug 2: Remote task result copy-back only checks size > 0Separately, and more concerning: even when the reject/reset path is not triggered and the remote encode succeeds cleanly (smaller than source, no reject warning in the log), the resulting on-disk file can still end up truncated to ~55 bytes.In installation_link.py, after a remote task completes, the finished file is staged by the remote installation into a unmanic_remote_pending_library-* directory on the shared library path, and the originating installation copies it back with:pythonoutput = shutil.copy(task_cache_path, correct_cache_file_path)if os.path.exists(output) and os.path.getsize(output) > 0: # treated as successThis only checks that the copied file is non-empty — it never compares the copied size against the expected/reported size from the remote task, and never waits for the remote-side write to fully settle before reading. Over a network/shared filesystem (NFS in our case) this is a real time-of-check-to-time-of-use race: the copy can read a stale/partially-written staged file and still pass the size > 0 check, silently corrupting media that was actually encoded successfully.## ImpactWe found 400+ TV episodes and dozens of movies replaced with ~55 byte files across our library, spanning weeks, before diagnosing this. Files pass task_success: true with no error surfaced anywhere in the Unmanic UI or history.## Suggested fixes1. For bug 1: don't reset from a path that may be a transient remote staging copy: cache the actual original file size/checksum before starting the remote task and re-verify against a durable copy, not a possibly-expired staging directory.2. For bug 2: after the shutil.copy in installation_link.py, verify the copied file's size matches the size reported by the remote task's completion data (or a checksum, if enable_checksum_validation is on) before accepting it as the delivered result. Fail loudly (and leave the original untouched) if it doesn't match.Happy to provide more logs/task IDs if useful — we have full unmanic.db history for both cases.
SummaryTwo separate bugs in the remote-task delivery pipeline (
unmanic/libs/installation_link.py/unmanic/libs/postprocessor.py) can silently replace a good source file with a tiny (~55 byte) truncated stub, destroying the original media with no error surfaced to the user. Found viaunmanic.dbhistory logs and confirmed by re-checking on-disk file sizes after "successful" tasks.Unmanic version:0.4.0+e838573(remote installation) /0.4.1~1c324b8(main installation)Plugin:video_transcoder(Transcode Video Files)## Bug 1: "Reject File if Larger than Original" reset reads from an expired staging pathWhen a remote-worker transcode produces output larger than the source, thereject_files_larger_than_originalplugin resets the task back to the original file. On long-running remote tasks (60-90+ min, e.g. an overloaded/slow hardware encoder), the "original" it resets to is a temp staging copy underunmanic_remote_pending_library-<random>— created for the remote handoff — which appears to no longer be intact/available by the time the reset runs. The result written back to the library is a few dozen bytes instead of the real source.Example from our logs (paths redacted):Resetting task file back to original source as current cache file is larger than the original file: - Original File: 463218706 bytes '.../unmanic_remote_pending_library-<redacted>/<file>.mkv' - Cache File: 1137643964 bytes '.../unmanic_file_conversion-<redacted>/<file>-WORKING-2-1.mkv'Runner did not request for Unmanic to execute a commandTask was markedtask_success: true. On-disk file after this task: 55 bytes.## Bug 2: Remote task result copy-back only checkssize > 0Separately, and more concerning: even when the reject/reset path is not triggered and the remote encode succeeds cleanly (smaller than source, no reject warning in the log), the resulting on-disk file can still end up truncated to ~55 bytes.Ininstallation_link.py, after a remote task completes, the finished file is staged by the remote installation into aunmanic_remote_pending_library-*directory on the shared library path, and the originating installation copies it back with:pythonoutput = shutil.copy(task_cache_path, correct_cache_file_path)if os.path.exists(output) and os.path.getsize(output) > 0: # treated as successThis only checks that the copied file is non-empty — it never compares the copied size against the expected/reported size from the remote task, and never waits for the remote-side write to fully settle before reading. Over a network/shared filesystem (NFS in our case) this is a real time-of-check-to-time-of-use race: the copy can read a stale/partially-written staged file and still pass thesize > 0check, silently corrupting media that was actually encoded successfully.## ImpactWe found 400+ TV episodes and dozens of movies replaced with ~55 byte files across our library, spanning weeks, before diagnosing this. Files passtask_success: truewith no error surfaced anywhere in the Unmanic UI or history.## Suggested fixes1. For bug 1: don't reset from a path that may be a transient remote staging copy: cache the actual original file size/checksum before starting the remote task and re-verify against a durable copy, not a possibly-expired staging directory.2. For bug 2: after theshutil.copyininstallation_link.py, verify the copied file's size matches the size reported by the remote task's completion data (or a checksum, ifenable_checksum_validationis on) before accepting it as the delivered result. Fail loudly (and leave the original untouched) if it doesn't match.Happy to provide more logs/task IDs if useful — we have fullunmanic.dbhistory for both cases.