langgenius / langgenius/dify

Workflow node/iteration/loop "finished" trace updates silently dropped in chat streaming (missing `-1` index guard in `handleSend`)

Open Beginner friendly
#38,980 2 comments 1 reaction 0 assignees View on GitHub
🐞 bug 1.16.0 project#dify
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.0-rc1 (also present on main @ 15acaf1fa6)

### Cloud or Self Hosted

Self Hosted (Source)

### Steps to reproduce

This is a logic bug in `web/app/components/base/chat/chat/hooks.ts`, in the streaming (`handleSend`) SSE callbacks used to build the workflow trace panel shown during/after a chat run.

`onNodeFinished`, `onIterationFinish`, and `onLoopFinish` all locate the matching "started" entry in `responseItem.workflowProcess.tracing` via `Array.prototype.findIndex(...)`, then write the finished data back with `tracing[index] = {...}`:

- `onNodeFinished` — `web/app/components/base/chat/chat/hooks.ts:1354-1368`
- `onIterationFinish` — `web/app/components/base/chat/chat/hooks.ts:1303-1316`
- `onLoopFinish` — `web/app/components/base/chat/chat/hooks.ts:1401-1414`

None of these three check that `findIndex` actually found a match (i.e. that the index is `> -1`) before writing to `tracing[index]`.

By contrast, the structurally identical callbacks used for `handleResume` (same file, ~150 lines earlier: `onNodeFinished` at line 664-681, `onIterationFinish` at line 645-651, `onLoopFinish` at line 705-724) all correctly guard the write with `if (currentIndex > -1)` / `if (iterationIndex > -1)` / `if (loopIndex > -1)`.

`Array.prototype.findIndex` returns `-1` when no element matches. In JavaScript, `arr[-1] = value` does **not** grow or replace an array element — it silently defines a non-index string property (`"-1"`) on the array object. The assignment does not throw, so this fails silently: the finished node/iteration/loop's data is never written into the tracing array shown to the user.

This is reachable whenever a `node_finished` / `iteration_finished` / `loop_finished` SSE event arrives during `handleSend` for a node whose corresponding "started" tracing entry was never pushed — e.g. an out-of-order or dropped SSE event, or a node id that doesn't match anything currently in `tracing` (this can also legitimately happen for nodes inside iterations/loops depending on ordering, since `onNodeStarted` explicitly skips pushing entries when `iteration_id`/`loop_id` is set — see lines 1338-1340).

There is already a test that exercises exactly this "no matching entry" case (`web/app/components/base/chat/chat/__tests__/hooks.spec.tsx:2899`, `sendCallbacks.onNodeFinished({ data: { id: 'missing-idx' } })`), but it only asserts that an unrelated field (`chatList[1].message_files`) is still defined — i.e. that nothing crashes — not that the tracing array is left unmodified. So the regression currently passes CI undetected.

### ✔️ Expected Behavior

When a `node_finished` / `iteration_finished` / `loop_finished` event has no matching "started" entry in the tracing array, the write should be skipped (guarded by `index > -1`), matching the behavior already implemented in the equivalent `handleResume` callbacks. The tracing array should never be corrupted with data assigned at index `-1`.

### ❌ Actual Behavior

In `handleSend`'s SSE callbacks, `onNodeFinished`, `onIterationFinish`, and `onLoopFinish` unconditionally write to `tracing[index]` even when `index === -1`, so the update is silently lost instead of being safely ignored (or the array being corrupted with a stray `-1` property, depending on how it's later serialized/consumed). This can make the workflow trace panel in the chat UI appear incomplete or stuck, with no error or console warning to indicate why.

Contributor guide

Open the contributing guide

Research direction

Start in web/app/components/base/chat/chat/hooks.ts, reviewing the handleSend callbacks around the reported lines and their guarded handleResume counterparts. Run the relevant cases in web/app/components/base/chat/chat/__tests__/hooks.spec.tsx, including the missing-idx case at line 2899. Done means unmatched node, iteration, and loop finish events leave tracing unchanged and the tests verify this behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend, testing
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
75/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.