Skip to content

fix(overlays): stop EPIPE crash and stabilise canvas previews#2832

Merged
enoch85 merged 4 commits into
developmentfrom
issue-2824
May 3, 2026
Merged

fix(overlays): stop EPIPE crash and stabilise canvas previews#2832
enoch85 merged 4 commits into
developmentfrom
issue-2824

Conversation

@SmolSoftBoi

@SmolSoftBoi SmolSoftBoi commented May 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Server: harden the SSE stream client so a late EPIPE on response.write (or on the underlying socket after an explicit close) is swallowed instead of bubbling to uncaughtException. This is the crash users hit when clicking Run now / Reset all overlays — stack trace at WriteWrap.onWriteComplete (node:internal/stream_base_commons:87:19) (Mantainerr goes down whenever I try to process or reset overlays #2824).
  • Server: add 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.
  • UI: keep overlay image assets loaded only when the set of image filenames actually changes — visibility toggles no longer evict the decoded bitmap (no placeholder flash on hide/show).
  • UI: preserve the saved overlay bounds for fitted image elements via a transparent full-size hit area in the canvas preview.
  • Tests: SSE late-error and explicit-close lifecycle, 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

@SmolSoftBoi SmolSoftBoi added the bug Something isn't working label May 3, 2026
@SmolSoftBoi SmolSoftBoi linked an issue May 3, 2026 that may be closed by this pull request
@SmolSoftBoi SmolSoftBoi marked this pull request as ready for review May 3, 2026 10:13
@SmolSoftBoi SmolSoftBoi requested a review from enoch85 as a code owner May 3, 2026 10:13
Copilot AI review requested due to automatic review settings May 3, 2026 10:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@enoch85

This comment was marked as outdated.

@github-actions

This comment was marked as outdated.

@enoch85

enoch85 commented May 3, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

I was also thinking 2 things:

  1. Since users reported that enabling disabling Overlays solved it, there might be caching issues of some sort. Did you investigate that?
  2. Would it be possible to remove overlays when an item is removed? That way we would avoid triggering the EPIPE all together since the data would be fresh

@SmolSoftBoi SmolSoftBoi left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@enoch85

This comment was marked as outdated.

@enoch85 enoch85 changed the title Stabilise overlay image previews and selection bounds fix(overlays): stop EPIPE crash and stabilise canvas previews May 3, 2026
… 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.
enoch85

This comment was marked as outdated.

@enoch85

This comment was marked as outdated.

@github-actions

github-actions Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor

Released to maintainerr/maintainerr:pr-2832 🚀

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Comment thread apps/server/src/modules/api/media-server/jellyfin/jellyfin-adapter.service.ts Outdated
Comment thread apps/server/src/utils/sse-stream.ts
…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.
@enoch85 enoch85 merged commit 4c62bde into development May 3, 2026
19 checks passed
@enoch85 enoch85 deleted the issue-2824 branch May 3, 2026 15:09
maintainerr-automation Bot added a commit that referenced this pull request May 3, 2026
* 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>
@maintainerr-automation

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.10.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

krezh pushed a commit to krezh/dextek that referenced this pull request May 4, 2026
…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 ([#&#8203;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
@enoch85 enoch85 added this to the 3.10.1 milestone May 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mantainerr goes down whenever I try to process or reset overlays

3 participants