{"record":{"id":"3782d66616f8a2fd","repo":"different-ai/openwork","slug":"transferid-is-already-active","errorCode":null,"errorMessage":"transferId is already active.","messagePattern":"transferId is already active\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/main.mjs","lineNumber":1085,"sourceCode":"  app,\n  defaultDenBaseUrl: DEFAULT_DEN_BASE_URL,\n  defaultRequireSignin: DEFAULT_DESKTOP_REQUIRE_SIGNIN,\n  forceRequireSignin: FORCE_DESKTOP_REQUIRE_SIGNIN,\n});\n\nconst activeDesktopTransfers = new Map();\n\nfunction desktopTransferKey(event, transferId) {\n  const normalizedId = typeof transferId === \"string\" ? transferId.trim() : \"\";\n  if (!normalizedId || normalizedId.length > 128 || !/^[a-zA-Z0-9._-]+$/.test(normalizedId)) {\n    throw new Error(\"A valid transferId is required.\");\n  }\n  return `${event.sender.id}:${normalizedId}`;\n}\n\nasync function runDesktopTransfer(event, input, operation) {\n  const key = desktopTransferKey(event, input?.transferId);\n  if (activeDesktopTransfers.has(key)) throw new Error(\"transferId is already active.\");\n  const controller = new AbortController();\n  const abort = () => controller.abort();\n  activeDesktopTransfers.set(key, controller);\n  event.sender.once(\"destroyed\", abort);\n  try {\n    // Both authorities come from app-owned state in userData; workspace-\n    // writable configuration must never widen where a transfer may write.\n    const [authorizedRoots, allowedUrlPrefixes] = await Promise.all([\n      workspaceStore.listLocalWorkspacePaths(),\n      workspaceStore.listRemoteWorkspaceUrlPrefixes(),\n    ]);\n    return await operation(input, {\n      authorizedRoots,\n      allowedUrlPrefixes,\n      // App-owned staging keeps in-flight downloads outside every authorized\n      // workspace root until they complete.\n      stagingDir: path.join(app.getPath(\"userData\"), \"binary-transfers\"),\n      fetcher: electronNet.fetch,","sourceCodeStart":1067,"sourceCodeEnd":1103,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/desktop/electron/main.mjs#L1067-L1103","documentation":"runDesktopTransfer() keys in-flight transfers by `${event.sender.id}:${transferId}` in activeDesktopTransfers. If a transfer with the same key is already running, it throws this error to prevent concurrent duplicate work on the same sender/transfer pair. The entry is removed in the finally block when the transfer finishes.","triggerScenarios":"An IPC transfer call arrives while a previous call with the same sender id and transferId is still pending — e.g. double-click retry, renderer re-issuing after a slow response, or a stale transfer that never completed (aborts only fire on sender destruction).","commonSituations":"Users clicking a download/export button twice; the renderer not awaiting the first result before retrying; a hung operation never settling so the key stays in the map; React StrictMode double-invocation of effects.","solutions":["Use a fresh unique transferId for each new operation instead of reusing one.","Wait for the first transfer's completion before retrying; disable the triggering button while pending.","If a transfer is stuck, restart the renderer/webContents (sender destruction aborts and clears the key).","Add renderer-side state so an already-active transfer shows progress instead of re-invoking."],"exampleFix":"// before\nawait invoke('desktop:transfer', { transferId: 'export' }); // second call throws\n// after\nconst transferId = crypto.randomUUID(); // unique per attempt\nawait invoke('desktop:transfer', { transferId });","handlingStrategy":"retry","validationCode":"// track in-flight ids renderer-side before re-invoking\nconst inFlight = new Set();\nif (inFlight.has(transferId)) return; // skip duplicate invocation","typeGuard":null,"tryCatchPattern":"try {\n  await invoke('desktop:transfer', { transferId });\n} catch (err) {\n  if (String(err.message) === 'transferId is already active.') {\n    await new Promise(r => setTimeout(r, 250)); // then retry with a NEW transferId\n  } else throw err;\n}","preventionTips":["Issue a fresh transferId per operation attempt.","Disable triggering UI while a transfer is pending.","Always await the IPC result before allowing retries.","Avoid StrictMode double-invocation of transfer effects (guard with a ref)."],"tags":["ipc","concurrency","electron"],"backgroundTag":"duplicate-operation-in-progress","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}