LLM node loses prompt changes when calling /workflows/draft API due to stale closure values #20012

Closed
opened 2026-02-21 20:05:23 -05:00 by yindo · 1 comment
Owner

Originally created by @issac2e on GitHub (Oct 29, 2025).

Self Checks

  • I have read the Contributing Guide and Language Policy.
  • This is only for bug report, if you would like to ask a question, please head to Discussions.
  • I have searched for existing issues search for existing issues, including closed ones.
  • I confirm that I am using English to submit this report, otherwise it will be closed.
  • 【中文用户 & Non English User】请使用英语提交,否则会被关闭 :)
  • Please do not modify this template :) and fill in all the required fields.

Dify version

1.9.2

Cloud or Self Hosted

Self Hosted (Docker), Self Hosted (Source), Cloud

Steps to reproduce

  1. Create a new workflow with an LLM node
  2. Open the LLM node configuration panel
  3. Quickly edit the prompt content in the ConfigPrompt component multiple times within 5 seconds
  4. Wait for the debounced sync to trigger (5 seconds)
  5. Check the POST request to /apps/{appId}/workflows/draft in DevTools Network tab

Observed: The API request payload contains old prompt values, not the latest changes from the UI

✔️ Expected Behavior

When editing the prompt in the LLM node, the /workflows/draft API should receive the latest prompt content from the UI, regardless of how quickly the user types or makes changes.

The handlePromptChange callback should always operate on the most current node data to prevent data loss.

Actual Behavior

The /workflows/draft API receives stale/old prompt values instead of the latest changes.

Root Cause:
In web/app/components/workflow/nodes/llm/use-config.ts, several callback functions use closure-captured inputs variable instead of inputRef.current, causing them to operate on outdated data:

  • handlePromptChange (line ~250)
  • handleContextVarChange (line ~242)
  • handleMemoryChange (line ~257)
  • handleSyeQueryChange (line ~264)
  • handleCompletionParamsChange (line ~151)
  • handleStructureOutputEnableChange (line ~290)
  • handleStructureOutputChange (line ~300)
  • Vision onChange callback (line ~120)

Example of the bug:

// ❌ Current (wrong) - uses stale closure value
const handlePromptChange = useCallback((newPrompt: PromptItem[] | PromptItem) => {
  const newInputs = produce(inputs, (draft) => {  // 'inputs' is stale
    draft.prompt_template = newPrompt
  })
  setInputs(newInputs)
}, [inputs, setInputs])

// ✅ Should be (correct) - uses latest value
const handlePromptChange = useCallback((newPrompt: PromptItem[] | PromptItem) => {
  const newInputs = produce(inputRef.current, (draft) => {  // Always up-to-date
    draft.prompt_template = newPrompt
  })
  setInputs(newInputs)
}, [setInputs])

This inconsistency exists because other handlers in the same file (like handleAddEmptyVariable, handleVarListChange, handleModelChanged) already correctly use inputRef.current.

Originally created by @issac2e on GitHub (Oct 29, 2025). ### 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.9.2 ### Cloud or Self Hosted Self Hosted (Docker), Self Hosted (Source), Cloud ### Steps to reproduce 1. Create a new workflow with an LLM node 2. Open the LLM node configuration panel 3. Quickly edit the prompt content in the ConfigPrompt component multiple times within 5 seconds 4. Wait for the debounced sync to trigger (5 seconds) 5. Check the POST request to `/apps/{appId}/workflows/draft` in DevTools Network tab **Observed**: The API request payload contains old prompt values, not the latest changes from the UI ### ✔️ Expected Behavior When editing the prompt in the LLM node, the `/workflows/draft` API should receive the **latest prompt content** from the UI, regardless of how quickly the user types or makes changes. The `handlePromptChange` callback should always operate on the most current node data to prevent data loss. ### ❌ Actual Behavior The `/workflows/draft` API receives **stale/old prompt values** instead of the latest changes. **Root Cause**: In `web/app/components/workflow/nodes/llm/use-config.ts`, several callback functions use closure-captured `inputs` variable instead of `inputRef.current`, causing them to operate on outdated data: - `handlePromptChange` (line ~250) - `handleContextVarChange` (line ~242) - `handleMemoryChange` (line ~257) - `handleSyeQueryChange` (line ~264) - `handleCompletionParamsChange` (line ~151) - `handleStructureOutputEnableChange` (line ~290) - `handleStructureOutputChange` (line ~300) - Vision `onChange` callback (line ~120) **Example of the bug**: ```typescript // ❌ Current (wrong) - uses stale closure value const handlePromptChange = useCallback((newPrompt: PromptItem[] | PromptItem) => { const newInputs = produce(inputs, (draft) => { // 'inputs' is stale draft.prompt_template = newPrompt }) setInputs(newInputs) }, [inputs, setInputs]) // ✅ Should be (correct) - uses latest value const handlePromptChange = useCallback((newPrompt: PromptItem[] | PromptItem) => { const newInputs = produce(inputRef.current, (draft) => { // Always up-to-date draft.prompt_template = newPrompt }) setInputs(newInputs) }, [setInputs]) ``` This inconsistency exists because other handlers in the same file (like `handleAddEmptyVariable`, `handleVarListChange`, `handleModelChanged`) already correctly use `inputRef.current`.
yindo added the 🐞 bug label 2026-02-21 20:05:23 -05:00
yindo closed this issue 2026-02-21 20:05:23 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Oct 29, 2025):

The root cause is confirmed: using closure-captured inputs in React callbacks leads to stale state being sent to the /workflows/draft API, especially during rapid edits or debounced updates. The correct fix is to replace all instances of inputs in affected handlers with inputRef.current, which always references the latest state.

The following handlers in web/app/components/workflow/nodes/llm/use-config.ts are still affected and should be updated: handleContextVarChange, handleMemoryChange, handleSyeQueryChange, handleCompletionParamsChange, handleStructureOutputEnableChange, handleStructureOutputChange, and the Vision onChange callback. Only handlePromptChange has already been fixed in a recent PR by switching to inputRef.current (see PR #24561).

For maintainers: update all these handlers to use inputRef.current instead of closure-captured inputs to ensure the latest prompt and config data is always sent to the API. This pattern is already followed in other handlers like handleAddEmptyVariable, handleVarListChange, and handleModelChanged, which do not suffer from this bug (source).

Consider adding a code comment or documentation note in the file to highlight this best practice for future contributors. Automated linting or a checklist for React closure usage in debounced/asynchronous handlers could also help prevent similar bugs.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Oct 29, 2025): <!-- Answer --> The root cause is confirmed: using closure-captured `inputs` in React callbacks leads to stale state being sent to the `/workflows/draft` API, especially during rapid edits or debounced updates. The correct fix is to replace all instances of `inputs` in affected handlers with `inputRef.current`, which always references the latest state. The following handlers in `web/app/components/workflow/nodes/llm/use-config.ts` are still affected and should be updated: `handleContextVarChange`, `handleMemoryChange`, `handleSyeQueryChange`, `handleCompletionParamsChange`, `handleStructureOutputEnableChange`, `handleStructureOutputChange`, and the Vision `onChange` callback. Only `handlePromptChange` has already been fixed in a recent PR by switching to `inputRef.current` ([see PR #24561](https://github.com/langgenius/dify/pull/24561)). For maintainers: update all these handlers to use `inputRef.current` instead of closure-captured `inputs` to ensure the latest prompt and config data is always sent to the API. This pattern is already followed in other handlers like `handleAddEmptyVariable`, `handleVarListChange`, and `handleModelChanged`, which do not suffer from this bug ([source](https://github.com/langgenius/dify/blob/4ca7ba000cd497902fc957de4c3cf237cfdfa164/web/app/components/workflow/nodes/llm/use-config.ts)). Consider adding a code comment or documentation note in the file to highlight this best practice for future contributors. Automated linting or a checklist for React closure usage in debounced/asynchronous handlers could also help prevent similar bugs. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/17f4f569-e22d-4287-b23c-18b001a3cfa1?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/17f4f569-e22d-4287-b23c-18b001a3cfa1?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/17f4f569-e22d-4287-b23c-18b001a3cfa1?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/17f4f569-e22d-4287-b23c-18b001a3cfa1?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/17f4f569-e22d-4287-b23c-18b001a3cfa1?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/17f4f569-e22d-4287-b23c-18b001a3cfa1?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/17f4f569-e22d-4287-b23c-18b001a3cfa1?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/6088a4f4-073a-43f3-b3d4-4f06fb0c05a0/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify/issues/27612)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langgenius/dify#20012