fix(overlays): stop EPIPE crash and stabilise canvas previews#2832
Conversation
There was a problem hiding this comment.
Pull request overview
This PR stabilizes overlay editor previews by reducing unnecessary image reloads and ensuring image elements retain their full selectable bounds even when assets are rendered with a “contain” fit. It also adds focused UI/unit coverage to prevent regressions in both the UI and SSE stream handling.
Changes:
- Make overlay image loading depend on the set of visible image filenames (plus
imageLoadVersion), instead of rerunning on unrelated element edits. - Preserve full image element hit/transform bounds by adding a transparent full-size rect behind fitted image rendering.
- Add targeted tests for OverlayCanvas image bounds/reload behavior and for SSE error/listener lifecycle.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| apps/ui/src/components/OverlayEditor/OverlayCanvas.tsx | Stabilizes image asset loading inputs and adds transparent hit-area rect to keep fitted image bounds selectable. |
| apps/ui/src/components/OverlayEditor/OverlayCanvas.spec.tsx | Adds UI coverage for fitted-image bounds and verifies image assets don’t reload on unrelated edits. |
| apps/server/src/utils/sse-stream.ts | Adjusts SSE client error/close handling to better guard late errors and control listener detachment timing. |
| apps/server/src/utils/sse-stream.spec.ts | Adds unit coverage for guarding late response/socket errors and listener detachment on close. |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
|
Thanks for the PR! I was also thinking 2 things:
|
SmolSoftBoi
left a comment
There was a problem hiding this comment.
I updated to pr-2832 and Maintainerr is no longer restarting every time I hit "Run now", however it shows Errors: 2
It looks like it's trying to process overlay for items already removed. Logs show the following:
[maintainerr] | 03/05/2026 07:39:48 [INFO] [RuleExecutorService] Starting execution of rule 'Watched movies to be deleted' [maintainerr] | 03/05/2026 07:39:48 [INFO] [RuleExecutorService] Executing rules for 'Watched movies to be deleted' [maintainerr] | 03/05/2026 07:39:50 [DEBUG] [CollectionsService] [checkAutomaticMediaServerLink] Collection "Watched movies to be deleted" (DB id: 1, mediaServerId: 1459) [maintainerr] | 03/05/2026 07:39:50 [DEBUG] [CollectionsService] [checkAutomaticMediaServerLink] getCollection(1459) returned: id=1459, childCount=1 [maintainerr] | 03/05/2026 07:39:50 [DEBUG] [CollectionsService] [checkAutomaticMediaServerLink] Trusting Plex metadata childCount=1 for collection 1459, keeping it [maintainerr] | 03/05/2026 07:39:50 [INFO] [RuleExecutorService] Adding 1 media items to 'Watched movies to be deleted'. [maintainerr] | 03/05/2026 07:39:50 [INFO] [CollectionsService] Adding 1 media items to collection.. [maintainerr] | 03/05/2026 07:39:50 [INFO] [EmailAgent] Sending email notification [maintainerr] | 03/05/2026 07:39:50 [INFO] [RuleExecutorService] Execution of rules for 'Watched movies to be deleted' done. [maintainerr] | 03/05/2026 07:39:50 [DEBUG] [CollectionsService] [checkAutomaticMediaServerLink] Collection "Watched movies to be deleted" (DB id: 1, mediaServerId: 1459) [maintainerr] | 03/05/2026 07:39:50 [DEBUG] [CollectionsService] [checkAutomaticMediaServerLink] getCollection(1459) returned: id=1459, childCount=2 [maintainerr] | 03/05/2026 07:39:50 [DEBUG] [CollectionsService] [checkAutomaticMediaServerLink] Trusting Plex metadata childCount=2 for collection 1459, keeping it [maintainerr] | 03/05/2026 07:39:50 [INFO] [RuleExecutorService] Synced collection 'Watched movies to be deleted' with media server [maintainerr] | 03/05/2026 07:40:02 [INFO] [OverlayProcessorService] === Overlay processor started === [maintainerr] | 03/05/2026 07:40:02 [INFO] [OverlayProcessorService] Processing 1 overlay-enabled collection(s) [maintainerr] | 03/05/2026 07:40:02 [INFO] [OverlayProcessorService] Item 1392 no longer in any overlay collection, reverting [maintainerr] | 03/05/2026 07:40:02 [DEBUG] [PlexApiService] getPosters(1392) failed: Error: GET http://****:32400/li...ers failed with exception: Plex Server didnt respond with a valid 2xx status code, response code: 404 [maintainerr] | 03/05/2026 07:40:02 [WARN] [OverlayProcessorService] Failed to restore original poster for 1392; keeping backup for retry [maintainerr] | 03/05/2026 07:40:02 [DEBUG] [OverlayProcessorService] write EPIPE | code=EPIPE Error: write EPIPE at AxiosError.from (/opt/app/node_modules/axios/dist/node/axios.cjs:881:24) at RedirectableRequest.handleRequestError (/opt/app/node_modules/axios/dist/node/axios.cjs:3397:25) at RedirectableRequest.emit (node:events:509:28) at emitErrorNT (node:internal/streams/destroy:170:8) at emitErrorCloseNT (node:internal/streams/destroy:129:3) at process.processTicksAndRejections (node:internal/process/task_queues:90:21) at Axios.request (/opt/app/node_modules/axios/dist/node/axios.cjs:4517:41) at process.processTicksAndRejections (node:internal/process/task_queues:104:5) at async PlexApiService.uploadPoster (/opt/app/apps/server/dist/modules/api/plex-api/plex-api.service.js:1091:9) at async PlexApiService.setThumb (/opt/app/apps/server/dist/modules/api/plex-api/plex-api.service.js:1146:9) at async PlexOverlayProvider.uploadImage (/opt/app/apps/server/dist/modules/overlays/providers/plex-overlay.provider.js:48:9) at async OverlayProcessorService.revertItemInternal (/opt/app/apps/server/dist/modules/overlays/overlay-processor.service.js:132:13) at async OverlayProcessorService.processAllCollections (/opt/app/apps/server/dist/modules/overlays/overlay-processor.service.js:287:40) at async OverlaysController.processAll (/opt/app/apps/server/dist/modules/overlays/overlays.controller.js:157:24) at async /opt/app/node_modules/@nestjs/core/router/router-execution-context.js:46:28 at async /opt/app/node_modules/@nestjs/core/router/router-proxy.js:9:17 [maintainerr] | 03/05/2026 07:40:02 [INFO] [OverlayProcessorService] Item 1408 no longer in any overlay collection, reverting [maintainerr] | 03/05/2026 07:40:02 [DEBUG] [PlexApiService] getPosters(1408) failed: Error: GET http://****:32400/li...ers failed with exception: Plex Server didnt respond with a valid 2xx status code, response code: 404 [maintainerr] | 03/05/2026 07:40:02 [WARN] [OverlayProcessorService] Failed to restore original poster for 1408; keeping backup for retry [maintainerr] | 03/05/2026 07:40:02 [DEBUG] [OverlayProcessorService] Request failed with status code 404 | code=ERR_BAD_REQUEST | status=404 Not Found AxiosError: Request failed with status code 404 at settle (/opt/app/node_modules/axios/dist/node/axios.cjs:1970:12) at Unzip.handleStreamEnd (/opt/app/node_modules/axios/dist/node/axios.cjs:3377:11) at Unzip.emit (node:events:509:28) at endReadableNT (node:internal/streams/readable:1729:12) at process.processTicksAndRejections (node:internal/process/task_queues:90:21) at Axios.request (/opt/app/node_modules/axios/dist/node/axios.cjs:4517:41) at process.processTicksAndRejections (node:internal/process/task_queues:104:5) at async PlexApiService.uploadPoster (/opt/app/apps/server/dist/modules/api/plex-api/plex-api.service.js:1091:9) at async PlexApiService.setThumb (/opt/app/apps/server/dist/modules/api/plex-api/plex-api.service.js:1146:9) at async PlexOverlayProvider.uploadImage (/opt/app/apps/server/dist/modules/overlays/providers/plex-overlay.provider.js:48:9) at async OverlayProcessorService.revertItemInternal (/opt/app/apps/server/dist/modules/overlays/overlay-processor.service.js:132:13) at async OverlayProcessorService.processAllCollections (/opt/app/apps/server/dist/modules/overlays/overlay-processor.service.js:287:40) at async OverlaysController.processAll (/opt/app/apps/server/dist/modules/overlays/overlays.controller.js:157:24) at async /opt/app/node_modules/@nestjs/core/router/router-execution-context.js:46:28 at async /opt/app/node_modules/@nestjs/core/router/router-proxy.js:9:17 [maintainerr] | 03/05/2026 07:40:02 [INFO] [OverlayProcessorService] --- Processing: "Watched movies to be deleted" (2 items) --- [maintainerr] | 03/05/2026 07:40:02 [INFO] [OverlayProcessorService] Collection "Watched movies to be deleted" using template "Classic Pill" (poster) [maintainerr] | 03/05/2026 07:40:02 [INFO] [OverlayProcessorService] Applying template overlay to item 1472 — 3 day(s) left [maintainerr] | 03/05/2026 07:40:03 [INFO] [OverlayProcessorService] === Overlay run complete: 1 applied, 0 reverted, 1 skipped, 2 errors ===
Originally posted by @OlivierChung in #2824
This comment was marked as outdated.
This comment was marked as outdated.
… cache across visibility toggles Skip the revert upload when the media server explicitly reports the item gone (404 / empty result), drop the now-stale state and backup quietly so we don't retry forever, and keep the OverlayCanvas image cache keyed on the full set of referenced filenames so hiding an element no longer evicts its decoded bitmap.
This comment was marked as outdated.
This comment was marked as outdated.
|
Released to |
…ists Returning false when the adapter is not initialised broke the contract that false is reserved for an explicit "item gone" signal. Revert callers (revertCollection, resetAllOverlays, revertMultipleItems) act on false by deleting the on-disk poster backup and state row, which would wipe every backup if a user triggered revert while Jellyfin was briefly unconfigured. Throw instead so revert preserves state for a later retry — matching how transient 5xx / network errors are handled.
* build(deps): bump zod from 4.3.6 to 4.4.1 (#2820) Bumps [zod](https://github.com/colinhacks/zod) from 4.3.6 to 4.4.1. - [Release notes](https://github.com/colinhacks/zod/releases) - [Commits](colinhacks/zod@v4.3.6...v4.4.1) --- updated-dependencies: - dependency-name: zod dependency-version: 4.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump jsdom from 29.1.0 to 29.1.1 (#2821) Bumps [jsdom](https://github.com/jsdom/jsdom) from 29.1.0 to 29.1.1. - [Release notes](https://github.com/jsdom/jsdom/releases) - [Commits](jsdom/jsdom@v29.1.0...v29.1.1) --- updated-dependencies: - dependency-name: jsdom dependency-version: 29.1.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump @tanstack/react-query from 5.99.2 to 5.100.6 (#2822) Bumps [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) from 5.99.2 to 5.100.6. - [Release notes](https://github.com/TanStack/query/releases) - [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md) - [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.100.6/packages/react-query) --- updated-dependencies: - dependency-name: "@tanstack/react-query" dependency-version: 5.100.6 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump axios from 1.15.0 to 1.15.2 (#2828) Bumps [axios](https://github.com/axios/axios) from 1.15.0 to 1.15.2. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](axios/axios@v1.15.0...v1.15.2) --- updated-dependencies: - dependency-name: axios dependency-version: 1.15.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @tanstack/eslint-plugin-query (#2830) Bumps [@tanstack/eslint-plugin-query](https://github.com/TanStack/query/tree/HEAD/packages/eslint-plugin-query) from 5.100.6 to 5.100.7. - [Release notes](https://github.com/TanStack/query/releases) - [Changelog](https://github.com/TanStack/query/blob/main/packages/eslint-plugin-query/CHANGELOG.md) - [Commits](https://github.com/TanStack/query/commits/@tanstack/eslint-plugin-query@5.100.7/packages/eslint-plugin-query) --- updated-dependencies: - dependency-name: "@tanstack/eslint-plugin-query" dependency-version: 5.100.7 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump globals from 17.5.0 to 17.6.0 (#2829) Bumps [globals](https://github.com/sindresorhus/globals) from 17.5.0 to 17.6.0. - [Release notes](https://github.com/sindresorhus/globals/releases) - [Commits](sindresorhus/globals@v17.5.0...v17.6.0) --- updated-dependencies: - dependency-name: globals dependency-version: 17.6.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(overlays): stop EPIPE crash and stabilise canvas previews (#2832) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: maintainerr-automation[bot] <261505141+maintainerr-automation[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kristian Matthews-Kennington <kristian@matthews-kennington.com>
|
🎉 This PR is included in version 3.10.1 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
…0.1 ) (#29) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/maintainerr/maintainerr](https://github.com/Maintainerr/Maintainerr) | patch | `3.10.0` → `3.10.1` | --- ### Release Notes <details> <summary>Maintainerr/Maintainerr (ghcr.io/maintainerr/maintainerr)</summary> ### [`v3.10.1`](https://github.com/Maintainerr/Maintainerr/releases/tag/v3.10.1) [Compare Source](Maintainerr/Maintainerr@v3.10.0...v3.10.1) ##### Fixes - Fix EPIPE crash and stabilize canvas previews by hardening SSE stream client to handle late EPIPE errors gracefully ([#​2832](Maintainerr/Maintainerr#2832)). ##### Dependencies - Updated 6 dependencies, including notable packages: `axios`, `@tanstack/react-query`, and `zod`. </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/Stockholm) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNjAuNiIsInVwZGF0ZWRJblZlciI6IjQzLjE2MC43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://codeberg.org/dextek/dextek/pulls/29
Summary
response.write(or on the underlying socket after an explicit close) is swallowed instead of bubbling touncaughtException. This is the crash users hit when clicking Run now / Reset all overlays — stack traceat WriteWrap.onWriteComplete (node:internal/stream_base_commons:87:19)(Mantainerr goes down whenever I try to process or reset overlays #2824).IOverlayProvider.itemExists()(Plex + Jellyfin) and short-circuit revert when the media-server explicitly reports the item gone (404 / empty result). This stops the per-run EPIPE / 404 spam against deleted items, drops the now-stale state and backup, and removes the "Errors: 2" phantom counts seen in Mantainerr goes down whenever I try to process or reset overlays #2824 follow-up logs. Conservative: transient failures (5xx, network, auth) rethrow so a blip never drops a backup we'll need on the next run.itemExists(Plex API, Jellyfin adapter, both providers, processor revert flow), canvas image bounds, canvas reload-on-unrelated-edit, canvas cache retention across visibility toggles.Fixes
Closes #2824