{"record":{"id":"f474344fd1d0cfce","repo":"lobehub/lobehub","slug":"conflict-f47434","errorCode":"CONFLICT","errorMessage":"A previous transfer of this group's agents is still migrating history","messagePattern":"A previous transfer of this group's agents is still migrating history","errorType":"exception","errorClass":"TRPCError","httpStatus":409,"severity":"warning","filePath":"apps/server/src/routers/lambda/agentGroup.ts","lineNumber":715,"sourceCode":"          action: 'edit',\n          db: ctx.serverDB,\n          resourceId: input.groupId,\n          resourceType: 'agentGroup',\n          userId: ctx.userId,\n          workspaceId: ctx.workspaceId,\n        });\n      }\n      try {\n        return await ctx.agentGroupRepo.removeAgentsFromGroup(\n          input.groupId,\n          input.agentIds,\n          input.deleteVirtualAgents,\n        );\n      } catch (error) {\n        // A backfill still maps these agents' message rows — removing a member\n        // now would strand the job on a dangling `messages.agent_id`.\n        if (error instanceof Error && error.message === AGENT_TRANSFER_IN_PROGRESS) {\n          throw new TRPCError({\n            code: 'CONFLICT',\n            message: \"A previous transfer of this group's agents is still migrating history\",\n          });\n        }\n        if (error instanceof Error && error.message === AGENT_COPY_IN_PROGRESS) {\n          throw new TRPCError({\n            code: 'CONFLICT',\n            message: 'A previous copy of this agent is still duplicating its history',\n          });\n        }\n        throw error;\n      }\n    }),\n\n  /**\n   * Members these groups only reference (the roster's `External` rows).\n   *\n   * Asked before a transfer: those agents stay in the source scope and the","sourceCodeStart":697,"sourceCodeEnd":733,"githubUrl":"https://github.com/lobehub/lobehub/blob/10f24d7ade75139093a9373b364f6bc91f3cd7db/apps/server/src/routers/lambda/agentGroup.ts#L697-L733","documentation":"Thrown by removeAgentsFromGroup when an in-flight agent-transfer backfill job still maps one or more of the target agents' message rows. Removing a member now would leave the job pointing at a dangling messages.agent_id, so the repo raises AGENT_TRANSFER_IN_PROGRESS and the router translates it to HTTP 409 CONFLICT. This is a transient state that resolves when the backfill completes.","triggerScenarios":"Calling removeAgentsFromGroup with agentIds that include agents currently being moved by a recent transferGroup call. The repo error message AGENT_TRANSFER_IN_PROGRESS is matched literally — any other error is rethrown unchanged.","commonSituations":"A user transfers a group between workspaces and immediately tries to prune members before the async history backfill finishes. An automation pipeline fires transfer then edit in quick succession. A previous transfer crashed leaving a stuck job row.","solutions":["Retry the removal after the transfer backfill completes (watch the job status or poll agentGroupRepo for the in-progress flag to clear).","Inspect agentTransferJob rows for the affected agentIds; if a job is stuck (status not advancing, no heart-beat), escalate to an admin to reap or replay it, then retry.","If you must remove members immediately, transfer them fully first (complete the job) rather than racing it."],"exampleFix":"// before\nawait trpc.agentGroup.removeAgentsFromGroup.mutate({ groupId, agentIds, deleteVirtualAgents: true });\n// after — surface the transient state and retry\ntry {\n  await trpc.agentGroup.removeAgentsFromGroup.mutate({ groupId, agentIds, deleteVirtualAgents: true });\n} catch (e) {\n  if (e.data?.code === 'CONFLICT' && /still migrating history/.test(e.message)) {\n    // backoff and retry, or prompt the user that a transfer is finishing\n  } else throw e;\n}","handlingStrategy":"retry","validationCode":"// Inspect agentTransferJob rows for the agents you intend to remove\nconst inFlight = await db.select()\n  .from(agentTransferJob)\n  .where(and(\n    inArray(agentTransferJob.agentId, agentIds),\n    notEquals(agentTransferJob.status, 'done'),\n  ));\nif (inFlight.length > 0) throw new Error('Transfer backfill still running');","typeGuard":null,"tryCatchPattern":"async function removeWithRetry(groupId, agentIds, opts) {\n  for (let attempt = 0; attempt < 5; attempt++) {\n    try {\n      return await trpc.agentGroup.removeAgentsFromGroup.mutate({ groupId, agentIds, ...opts });\n    } catch (e) {\n      const migrating = e.shape?.data?.code === 'CONFLICT' && /still migrating history/.test(e.message);\n      if (!migrating || attempt === 4) throw e;\n      await waitForTransferJobsToSettle(agentIds);\n    }\n  }\n}","preventionTips":["After a transferGroup call, wait for the transfer job to settle before pruning members.","Disable the 'remove members' control while a transfer is in flight on the group.","In automation, model transfers and roster edits as ordered phases, not concurrent tasks."],"tags":["agent-group","concurrency","transfer","backfill","conflict","trpc"],"backgroundTag":null,"analyzedSha":"10f24d7ade75139093a9373b364f6bc91f3cd7db","analyzedAt":"2026-08-12T11:43:19.543Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}