Code node: an output variable's declared type is destroyed when another row's name is typed through it
- Dominant language
- TypeScript
- Stars
- 156k
- Forks
- 24.6k
- Avg merge
- 22h 9m
- Merged PRs (30d)
- 610
Description
### Self Checks
- [x] I have read the [Contributing Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) and [Language Policy](https://github.com/langgenius/dify/issues/1542).
- [x] This is only for bug report, if you would like to ask a question, please head to [Discussions](https://github.com/langgenius/dify/discussions/categories/general).
- [x] I have searched for existing issues [search for existing issues](https://github.com/langgenius/dify/issues), including closed ones.
- [x] I confirm that I am using English to submit this report, otherwise it will be closed.
- [x] 【中文用户 & Non English User】请使用英语提交,否则会被关闭 :)
- [x] Please do not modify this template :) and fill in all the required fields.
### Dify version
1.16.1
### Cloud or Self Hosted
Self Hosted (Docker)
### Steps to reproduce
A Code node output variable loses its declared type if another row's name passes through its name while being typed. The name does not have to be *left* colliding — typing through the collision is enough.
1. New workflow, new **Code** node.
2. Output variable row 1: name `a`, type **`array[object]`**.
3. Click `+`. Row 2 appears, auto-named `var_2`, type `string`.
4. Clear row 2's name and type `ab`.
As the field passes through `a`, the toast `a already exists` appears — but the damage is already done.
After typing, row 1 still reads `a`, and its type now reads **`string`**. Reloading the page leaves two rows, `a` and `ab`, both `string`.
This is not a rendering problem. The exported DSL for the node holds:
```json
"outputs": {
"a": { "type": "string", "children": null },
"ab": { "type": "string", "children": null }
}
```
The `array[object]` declaration is gone from disk. Reproduced twice on 1.16.1.
**Reading the source for a likely cause** — offered as a pointer, not as something I observed at runtime:
`web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx`, `handleVarNameChange`:
```js
const newOutputs = produce(outputs, (draft) => {
draft[newKey] = draft[oldKey]!
if (!list.some((item, i) => i !== index && item.variable === oldKey)) delete draft[oldKey]
})
```
`outputs` is a `Record` while rows are positional via `outputKeyOrders`, so two rows cannot both be represented when they share a name. `draft[newKey] = draft[oldKey]` is unconditional, so when `newKey` already belongs to another row, that row's entry is replaced by the editing row's. The name check beside it is `useDebounceFn(..., { wait: 500 })` and only raises a toast, so the message arrives after the write.
**On #31170**, which reworks the same function: its gate does not cover this case. `validateVarInput` there returns `false` only when `checkKeys` fails; the duplicate-name branch raises the toast and then falls through to `return true`, so `if (!validateVarInput(...)) return` does not fire on a collision and the `produce` block still runs. That is consistent with what the PR is for — it fixes #31169, the 30-character limit.
## Screenshot/Video ##
https://github.com/user-attachments/assets/4a173bb4-5ec6-4d3c-8b4f-76e5881c5371
### ✔️ Expected Behavior
Typing a name that another output variable already has — including passing through it on the way to a different name — should leave the other variable's declared type alone. Typing itself should not be blocked, since a user often has to transit an existing name to reach a longer one.
### ❌ Actual Behavior
The other row's entry is overwritten with the editing row's, and the type it declared is lost. The loss reaches the saved workflow and the exported DSL. The `already exists` toast appears about half a second later, describing a variable whose declaration has already been replaced.
Contributor guide
Research direction
Start in web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx, read handleVarNameChange alongside outputKeyOrders and validateVarInput, then reproduce the reported typing sequence in a Code node. Done means passing through an existing output name does not replace that row’s declared type, the duplicate-name toast still appears, and the saved/exported DSL retains array[object].
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 75/100