{"record":{"id":"a772dfad2876fa5a","repo":"langgenius/dify","slug":"patched-environment-variables-require-an-id","errorCode":null,"errorMessage":"patched environment variables require an id","messagePattern":"patched environment variables require an id","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"api/controllers/console/app/workflow.py","lineNumber":128,"sourceCode":"\nclass EnvironmentVariableResponseDict(TypedDict):\n    value_type: str\n    id: NotRequired[str]\n    name: NotRequired[str]\n    value: NotRequired[Any]\n    description: NotRequired[str | None]\n\n\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\")","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/langgenius/dify/blob/ef8544b173fd6cd7a8e71df2cab576e52bebbfbc/api/controllers/console/app/workflow.py#L110-L146","documentation":"Raised by the model_validator on SyncEnvironmentVariablePatchPayload when any upserted environment variable lacks a non-empty string 'id' (api/controllers/console/app/workflow.py:126-128). The patch-merge protocol requires every upsert to carry a stable id so the service can deterministically merge it. Missing/non-string/empty ids are rejected at validation time before the service runs.","triggerScenarios":"POSTing/Syncing a draft workflow with environment_variable_patch.environment_variables entries that omit 'id', set it to null, or use a non-string value. Pydantic model_validate triggers the validator and re-raises as a ValidationError.","commonSituations":"Front-end creating a new env var in a patch without assigning a client-side UUID; migrating from the old full-replace payload that had no ids; null id sent for a 'new' variable; numeric id instead of string.","solutions":["Generate a stable UUID (v4) client-side for every environment variable included in the patch and include it as a string 'id'.","For existing variables being edited, reuse their existing id from the draft state.","Run the payload through the same validation rules client-side before sending."],"exampleFix":"// before: id missing on a new variable\n{environment_variables: [{name:'KEY', value:'v', value_type:'string'}]}\n// after: client-generated id\n{environment_variables: [{id: crypto.randomUUID(), name:'KEY', value:'v', value_type:'string'}]}","handlingStrategy":"validation","validationCode":"// Ensure every patched env var has a non-empty string id before submit\nfunction ensureIds(vars) {\n  return vars.map(v => ({...v, id: typeof v.id === 'string' && v.id ? v.id : crypto.randomUUID()}))\n}\npayload.environment_variable_patch.environment_variables = ensureIds(payload.environment_variable_patch.environment_variables)","typeGuard":"function isValidPatch(vars) {\n  return Array.isArray(vars) && vars.every(v => typeof v?.id === 'string' && v.id.length > 0)\n}","tryCatchPattern":"try { await syncDraft(appId, payload) } catch (e) {\n  if (/require an id/.test(e.message)) { payload = withIds(payload); retry() }\n  throw e\n}","preventionTips":["Always assign a client-side UUID when creating a variable.","Reuse existing ids when editing; never send null/empty.","Validate the patch shape on the client before submit."],"tags":["workflow","environment-variables","validation","pydantic","api"],"backgroundTag":null,"analyzedSha":"ef8544b173fd6cd7a8e71df2cab576e52bebbfbc","analyzedAt":"2026-08-12T05:15:17.394Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}