{"record":{"id":"b02debbf86d9e38a","repo":"jely2002/youtube-dl-gui","slug":"orphaned-media-item-found-during-error-handling","errorCode":null,"errorMessage":"Orphaned media item found during error handling.","messagePattern":"Orphaned media item found during error handling\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/stores/media/diagnostics.ts","lineNumber":27,"sourceCode":"export const useMediaDiagnosticsStore = defineStore('media-diagnostics', () => {\n  const diagnostics = ref<Record<string, MediaDiagnostic[]>>({});\n  const fatals = ref<Record<string, MediaFatal>>({});\n\n  const stateStore = useMediaStateStore();\n  const groupStore = useMediaGroupStore();\n  const mediaStore = useMediaStore();\n\n  function processMediaDiagnosticPayload(payload: MediaDiagnostic) {\n    diagnostics.value[payload.id] = diagnostics.value[payload.id] ?? [];\n    diagnostics.value[payload.id].push(payload);\n  }\n\n  function processMediaFatalPayload(payload: MediaFatal) {\n    fatals.value[payload.id] = payload;\n    const { groupId } = payload;\n    const currentState = stateStore.getGroupState(groupId);\n    const group = groupStore.findGroupById(groupId);\n    if (!group) throw new Error('Orphaned media item found during error handling.');\n    if (currentState === MediaState.fetching || currentState === MediaState.fetchingList) {\n      mediaStore.rejectPendingReadyGroup(groupId, payload.message);\n    }\n    group.processed++;\n    group.errored++;\n    const leader = groupStore.findGroupLeader(groupId);\n    if (leader && leader.entries) {\n      if (stateStore.getGroupState(groupId) === MediaState.fetchingList) {\n        return;\n      }\n      // We are combined, so we only set one item to error.\n      stateStore.setState(payload.id, MediaState.error);\n      const itemsWithoutLeader = Object.values(group.items).filter(item => !item.isLeader);\n      const allItemsAreTerminal = itemsWithoutLeader.every((item) => {\n        const state = stateStore.getState(item.id);\n        return state === MediaState.done || state === MediaState.error;\n      });\n      if (allItemsAreTerminal) {","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/jely2002/youtube-dl-gui/blob/c402ee39c0eabab68934c48f8b787ff37a1577b0/src/stores/media/diagnostics.ts#L9-L45","documentation":"processMediaFatalPayload records a fatal media error payload and then updates the owning group. If the group referenced by payload.groupId cannot be found in the group store, the store's invariants are broken (a media item exists without its parent group), so it throws 'Orphaned media item found during error handling.' This is an internal consistency guard, not an expected runtime condition.","triggerScenarios":"A MediaFatal payload arrives (via backend event) whose groupId no longer resolves through groupStore.findGroupById — e.g. the group was removed/cleared while a fatal error for one of its items was still in flight, or events arrive out of order during teardown.","commonSituations":"Removing/cancelling a download group while its media items are mid-fetch; clearing the group store on navigation/unmount while stale backend events still deliver fatal payloads; backend/group-store state desync after an app restart.","solutions":["Ensure groups are not removed from the group store while their media items can still emit fatal events (defer cleanup until pending fetches settle).","In the event listener that calls processMediaFatalPayload, check groupStore.findGroupById(payload.groupId) first and skip/ignore the payload when the group is gone.","Audit the code path that deletes groups (e.g. removeGroup/clearAll) to make sure it also rejects/cancels pending items so no orphaned payloads arrive afterwards.","Wrap the call in try/catch as a safety net so a stale event cannot crash error handling."],"exampleFix":"// before\nmediaEvents.on('fatal', (payload) => processMediaFatalPayload(payload));\n// after\nmediaEvents.on('fatal', (payload) => {\n  if (!groupStore.findGroupById(payload.groupId)) return; // stale event, ignore\n  processMediaFatalPayload(payload);\n});","handlingStrategy":"try-catch","validationCode":"if (!groupStore.findGroupById(payload.groupId)) return; // stale fatal payload, nothing to do","typeGuard":"const isKnownGroup = (groupId: string): boolean => groupStore.findGroupById(groupId) !== undefined;","tryCatchPattern":"try {\n  processMediaFatalPayload(payload);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('Orphaned media item')) {\n    console.warn('Ignoring fatal event for removed group', payload.groupId);\n    return;\n  }\n  throw e;\n}","preventionTips":["Never delete a group while its media items can still emit backend events.","Reject pending items before clearing the group store.","Guard event handlers with an existence check before store mutation."],"tags":["state-management","pinia","orphaned-entity","event-handling"],"backgroundTag":"entity-not-found","analyzedSha":"c402ee39c0eabab68934c48f8b787ff37a1577b0","analyzedAt":"2026-09-12T02:01:12.452Z","contentChangedAt":"2026-09-12T02:01:12.452Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}