langgenius/dify · error · ValueError
an environment variable cannot be upserted and deleted in…
Error message
an environment variable cannot be upserted and deleted in the same patch
What it means
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).
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))`.
Example fix
// before: id 'a' both upserted and deleted
{environment_variables:[{id:'a',...}], deleted_environment_variable_ids:['a']}
// after: pick one — here, keep the upsert, drop from deletes
{environment_variables:[{id:'a',...}], deleted_environment_variable_ids:[]} Defensive patterns
Strategy: validation
Validate before calling
// Reconcile: a variable cannot be both upserted and deleted const upsertIds = new Set(payload.environment_variables.map(v => v.id)) payload.deleted_environment_variable_ids = payload.deleted_environment_variable_ids.filter(id => !upsertIds.has(id))
Type guard
function disjoint(upserts, deletes) {
const u = new Set(upserts.map(v=>v.id)); return deletes.every(id => !u.has(id))
} Prevention
- Maintain a single source of truth for variable state (new/edited/deleted).
- When marking a variable deleted, drop it from the upsert set.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- deleted environment variable ids must be unique
- deleted environment variable ids must not be empty
- patched environment variable ids must be unique
- patched environment variables require an id
- Unsupported environment variable value type
AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12).
Data as JSON: /api/errors/5478703879994964.
Report an issue: GitHub.
Appendix: source
Thrown at api/controllers/console/app/workflow.py:136
class SyncEnvironmentVariablePatchPayload(BaseModel):
environment_variables: list[dict[str, Any]] = Field(default_factory=list)
deleted_environment_variable_ids: list[str] = Field(default_factory=list)
@model_validator(mode="after")
def validate_patch(self) -> Self:
"""Require stable, disjoint IDs so the service can merge the patch deterministically."""
upsert_ids = [variable.get("id") for variable in self.environment_variables]
if any(not isinstance(variable_id, str) or not variable_id for variable_id in upsert_ids):
raise ValueError("patched environment variables require an id")
if len(set(upsert_ids)) != len(upsert_ids):
raise ValueError("patched environment variable ids must be unique")
if any(not variable_id for variable_id in self.deleted_environment_variable_ids):
raise ValueError("deleted environment variable ids must not be empty")
if len(set(self.deleted_environment_variable_ids)) != len(self.deleted_environment_variable_ids):
raise ValueError("deleted environment variable ids must be unique")
if set(upsert_ids).intersection(self.deleted_environment_variable_ids):
raise ValueError("an environment variable cannot be upserted and deleted in the same patch")
return self
class SyncDraftWorkflowPayload(BaseModel):
model_config = ConfigDict(extra="forbid")
graph: dict[str, Any]
features: dict[str, Any]
hash: str | None = None
is_collaborative: bool = Field(default=False, alias="_is_collaborative")
environment_variable_patch: SyncEnvironmentVariablePatchPayload | None = None
conversation_variables: list[dict[str, Any]] = Field(
default_factory=list,
)
class BaseWorkflowRunPayload(BaseModel):
files: list[dict[str, Any]] | None = Field(default=None)View on GitHub (pinned to ef8544b173)