{"record":{"id":"5bb1e4c78bf77754","repo":"langgenius/dify","slug":"draft-workflow-not-sync-5bb1e4","errorCode":"draft_workflow_not_sync","errorMessage":"Workflow graph might have been modified, please refresh and resubmit.","messagePattern":"Workflow graph might have been modified, please refresh and resubmit\\.","errorType":"error_code","errorClass":"DraftWorkflowNotSync","httpStatus":409,"severity":"warning","filePath":"api/controllers/console/datasets/rag_pipeline/rag_pipeline_workflow.py","lineNumber":258,"sourceCode":"            )\n            environment_variables = [\n                variable_factory.build_environment_variable_from_mapping(obj) for obj in environment_variables_list\n            ]\n            conversation_variables_list = payload.conversation_variables or []\n            conversation_variables = [\n                variable_factory.build_conversation_variable_from_mapping(obj) for obj in conversation_variables_list\n            ]\n            workflow = rag_pipeline_service.sync_draft_workflow(\n                pipeline=pipeline,\n                graph=payload.graph,\n                unique_hash=payload.hash,\n                account=current_user,\n                environment_variables=environment_variables,\n                conversation_variables=conversation_variables,\n                rag_pipeline_variables=payload.rag_pipeline_variables or [],\n            )\n        except WorkflowHashNotEqualError:\n            raise DraftWorkflowNotSync()\n\n        return {\n            \"result\": \"success\",\n            \"hash\": workflow.unique_hash,\n            \"updated_at\": TimestampField().format(workflow.updated_at or workflow.created_at),\n        }\n\n\n@console_ns.route(\"/rag/pipelines/<uuid:pipeline_id>/workflows/draft/iteration/nodes/<string:node_id>/run\")\nclass RagPipelineDraftRunIterationNodeApi(Resource):\n    @console_ns.expect(console_ns.models[NodeRunPayload.__name__])\n    @console_ns.response(200, \"Success\", console_ns.models[RagPipelineOpaqueResponse.__name__])\n    @setup_required\n    @login_required\n    @account_initialization_required\n    @with_current_user\n    @get_rag_pipeline\n    @edit_permission_required","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/langgenius/dify/blob/ef8544b173fd6cd7a8e71df2cab576e52bebbfbc/api/controllers/console/datasets/rag_pipeline/rag_pipeline_workflow.py#L240-L276","documentation":"Returned (HTTP 409, error_code `draft_workflow_not_sync`, message 'Workflow graph might have been modified, please refresh and resubmit.') by POST /rag/pipelines/<pipeline_id>/workflows/draft when the supplied `hash` does not equal the current draft workflow's `unique_hash`. Dify uses optimistic concurrency: the client must echo the hash it last read so concurrent edits collide loudly rather than silently overwriting each other.","triggerScenarios":"Two editors editing the same draft concurrently; submitting a graph built on a stale snapshot; the draft was modified by a background process (e.g., migration) between read and write; client did not pass the hash from its previous GET.","commonSituations":"Multi-user editing. Long idle time between load and save. Tab left open while another session published or restored. Frontend forgot to thread the hash from the last sync response.","solutions":["Re-fetch the draft workflow (GET .../workflows/draft), rebase the local graph onto the latest version, and re-submit with the new hash.","Always pass the `hash` field returned by the most recent successful sync or GET.","Surface a 'workflow was modified by another editor' dialog and offer to merge or overwrite.","Serialize edits to a single pipeline to avoid concurrent sync collisions."],"exampleFix":"// before\nawait post(`/rag/pipelines/${pid}/workflows/draft`, { graph: localGraph, hash: staleHash })\n// after\nasync function syncDraft(graph) {\n  try {\n    return await post(`/rag/pipelines/${pid}/workflows/draft`, { graph, hash: currentHash })\n  } catch (e) {\n    if (e.code === 'draft_workflow_not_sync') {\n      const latest = await get(`/rag/pipelines/${pid}/workflows/draft`)\n      currentHash = latest.unique_hash\n      // rebase localGraph onto latest.graph, then retry once\n      return post(`/rag/pipelines/${pid}/workflows/draft`, { graph: rebased, hash: currentHash })\n    }\n    throw e\n  }\n}","handlingStrategy":"retry","validationCode":"// before submitting, ensure the hash you hold is current\nconst latest = await get(`${base}/draft`)\nif (latest.unique_hash !== localHash) {\n  // rebase localGraph onto latest.graph\n  localHash = latest.unique_hash\n}\nawait post(`${base}/draft`, { graph: localGraph, hash: localHash })","typeGuard":null,"tryCatchPattern":"try {\n  await post(`${base}/draft`, { graph, hash: currentHash })\n} catch (e) {\n  if (e.code === 'draft_workflow_not_sync') {\n    const latest = await get(`${base}/draft`)\n    currentHash = latest.unique_hash\n    // rebase graph, then retry once\n    await post(`${base}/draft`, { graph: rebased, hash: currentHash })\n  } else throw e\n}","preventionTips":["Always thread the `hash` from the most recent GET/sync response into the next POST.","Detect `draft_workflow_not_sync` and offer a merge/overwrite choice.","Serialize concurrent edits per pipeline to avoid repeated conflicts.","Avoid long idles between load and save; refresh before save."],"tags":["rag-pipeline","draft-workflow","optimistic-concurrency","conflict","api","rest"],"backgroundTag":null,"analyzedSha":"ef8544b173fd6cd7a8e71df2cab576e52bebbfbc","analyzedAt":"2026-08-12T05:15:17.394Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}