{"record":{"id":"5478703879994964","repo":"langgenius/dify","slug":"an-environment-variable-cannot-be-upserted-and-del","errorCode":null,"errorMessage":"an environment variable cannot be upserted and deleted in the same patch","messagePattern":"an environment variable cannot be upserted and deleted in the same patch","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"api/controllers/console/app/workflow.py","lineNumber":136,"sourceCode":"\nclass SyncEnvironmentVariablePatchPayload(BaseModel):\n    environment_variables: list[dict[str, Any]] = Field(default_factory=list)\n    deleted_environment_variable_ids: list[str] = Field(default_factory=list)\n\n    @model_validator(mode=\"after\")\n    def validate_patch(self) -> Self:\n        \"\"\"Require stable, disjoint IDs so the service can merge the patch deterministically.\"\"\"\n        upsert_ids = [variable.get(\"id\") for variable in self.environment_variables]\n        if any(not isinstance(variable_id, str) or not variable_id for variable_id in upsert_ids):\n            raise ValueError(\"patched environment variables require an id\")\n        if len(set(upsert_ids)) != len(upsert_ids):\n            raise ValueError(\"patched environment variable ids must be unique\")\n        if any(not variable_id for variable_id in self.deleted_environment_variable_ids):\n            raise ValueError(\"deleted environment variable ids must not be empty\")\n        if len(set(self.deleted_environment_variable_ids)) != len(self.deleted_environment_variable_ids):\n            raise ValueError(\"deleted environment variable ids must be unique\")\n        if set(upsert_ids).intersection(self.deleted_environment_variable_ids):\n            raise ValueError(\"an environment variable cannot be upserted and deleted in the same patch\")\n        return self\n\n\nclass SyncDraftWorkflowPayload(BaseModel):\n    model_config = ConfigDict(extra=\"forbid\")\n\n    graph: dict[str, Any]\n    features: dict[str, Any]\n    hash: str | None = None\n    is_collaborative: bool = Field(default=False, alias=\"_is_collaborative\")\n    environment_variable_patch: SyncEnvironmentVariablePatchPayload | None = None\n    conversation_variables: list[dict[str, Any]] = Field(\n        default_factory=list,\n    )\n\n\nclass BaseWorkflowRunPayload(BaseModel):\n    files: list[dict[str, Any]] | None = Field(default=None)","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/langgenius/dify/blob/ef8544b173fd6cd7a8e71df2cab576e52bebbfbc/api/controllers/console/app/workflow.py#L118-L154","documentation":"Raised by SyncEnvironmentVariablePatchPayload.validate_patch when the same id appears in both the upsert list and the delete list (api/controllers/console/app/workflow.py:135-136). Upserting and deleting the same variable in one patch is contradictory and would produce an undefined merge result, so the validator rejects it. Surfaced as a Pydantic ValidationError (400).","triggerScenarios":"A variable id is present in environment_variables (upsert) AND in deleted_environment_variable_ids in the same request. Happens when UI state allows a variable to be both edited and marked for deletion.","commonSituations":"User edits a variable then (via a stale action) marks it deleted; front-end keeps both a 'pending edit' and a 'pending delete' entry; merging two partial patches without reconciling conflicts; buggy undo flow.","solutions":["Reconcile client state: if a variable is marked for deletion, remove it from the upsert list (and vice versa).","Compute the patch deterministically from a single source of truth before submit.","Add a client-side intersection check: `upsertIds.filter(id => !deleteIds.includes(id))`."],"exampleFix":"// before: id 'a' both upserted and deleted\n{environment_variables:[{id:'a',...}], deleted_environment_variable_ids:['a']}\n// after: pick one — here, keep the upsert, drop from deletes\n{environment_variables:[{id:'a',...}], deleted_environment_variable_ids:[]}","handlingStrategy":"validation","validationCode":"// Reconcile: a variable cannot be both upserted and deleted\nconst upsertIds = new Set(payload.environment_variables.map(v => v.id))\npayload.deleted_environment_variable_ids = payload.deleted_environment_variable_ids.filter(id => !upsertIds.has(id))","typeGuard":"function disjoint(upserts, deletes) {\n  const u = new Set(upserts.map(v=>v.id)); return deletes.every(id => !u.has(id))\n}","tryCatchPattern":null,"preventionTips":["Maintain a single source of truth for variable state (new/edited/deleted).","When marking a variable deleted, drop it from the upsert set."],"tags":["workflow","environment-variables","validation","conflict","api"],"backgroundTag":null,"analyzedSha":"ef8544b173fd6cd7a8e71df2cab576e52bebbfbc","analyzedAt":"2026-08-12T05:15:17.394Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}