Skip to content

fix(internal): avoid panic and order-dependent concat for nil map values - #1182

Open
Lcos-000 wants to merge 1 commit into
cloudwego:mainfrom
Lcos-000:fix/concat-nil-panic-1181
Open

fix(internal): avoid panic and order-dependent concat for nil map values#1182
Lcos-000 wants to merge 1 commit into
cloudwego:mainfrom
Lcos-000:fix/concat-nil-panic-1181

Conversation

@Lcos-000

Copy link
Copy Markdown

ConcatItems panics with SIGSEGV when merging map[string]any stream chunks in which the same key is nil in an earlier chunk and set in a later chunk. The reverse ordering returns a misleading type-mismatch error instead. Both make concat order-dependent for the same stream content.

concatMaps collects every value (including nil) into rms[key] and hands the whole slice to toSliceValue. reflect.TypeOf(nil) yields a nil reflect.Type, which makes reflect.SliceOf(nil) panic; a trailing nil instead triggers the type-mismatch branch.

Filter nil values in concatMaps before toSliceValue: nil means the key is absent in that chunk and does not contribute. All-nil keeps the key with a zero value (SetMapIndex with nil would delete the key); one non-nil takes that value; multiple non-nil concat as before. This makes concat order-independent for the same content and removes the unreachable panic path.

What type of PR is this?

/kind fix

Check the PR title.

  • This PR title match the format: <type>(optional scope): <description>
  • The description of this PR title is user-oriented and clear enough for others to understand.
  • Attach the PR updating the user documentation if the current PR requires user awareness at the usage level. User docs repoConcatItems panics with SIGSEGV when merging map[string]any stream chunks in which the same key is nil in an earlier chunk and set in a later chunk. The reverse ordering returns a misleading type-mismatch error instead. Both make concat order-dependent for the same stream content.

concatMaps collects every value (including nil) into rms[key] and hands the whole slice to toSliceValue. reflect.TypeOf(nil) yields a nil reflect.Type, which makes reflect.SliceOf(nil) panic; a trailing nil instead triggers the type-mismatch branch.

Filter nil values in concatMaps before toSliceValue: nil means the key is absent in that chunk and does not contribute. All-nil keeps the key with a zero value (SetMapIndex with nil would delete the key); one non-nil takes that value; multiple non-nil concat as before. This makes concat order- independent for the same content and removes the unreachable panic path.

Closes #1181

What type of PR is this?

Check the PR title.

  • This PR title match the format: <type>(optional scope): <description>
  • The description of this PR title is user-oriented and clear enough for others to understand.
  • Attach the PR updating the user documentation if the current PR requires user awareness at the usage level. User docs repo

(Optional) Translate the PR title into Chinese.

(Optional) More detailed description for this PR(en: English/zh: Chinese).

en:
zh(optional):

(Optional) Which issue(s) this PR fixes:

(optional) The PR that updates user documentation:

PR title: fix(internal): avoid panic and order-dependent concat for nil map values

(Optional) Translate the PR title into Chinese.

修复合并 map[string]any 流式分片时因同一 key 出现 nil 值导致的 panic 与顺序相关行为。

(Optional) More detailed description for this PR(en: English/zh: Chinese).

en:
internal.ConcatItems is the unified stream-chunk concat entry shared by schema.ConcatMessages, schema.ConcatAgenticMessages (content-block Extra), and graph state merging. It panics with SIGSEGV (unrecoverable, no recover up the call stack) when merging map[string]any chunks where the same key is nil in an earlier chunk and a non-nil value in a later chunk; the reverse ordering returns the misleading error unexpected slice element type. Got string, expected <nil> and rejects a legitimately concatable stream. So merging the same content yields different results depending purely on chunk ordering.

Root cause: concatMaps collects every value (including nil) per key into rms[key] and hands the whole []any to toSliceValue. reflect.TypeOf(nil) yields a nil reflect.Type; reflect.SliceOf(nil) then SIGSEGVs (line 210). A trailing nil instead hits typ != vt (line 216) and returns the type-mismatch error.

Fix: filter nil in concatMaps before calling toSliceValuenil means the key is absent in that chunk and does not contribute. Then:

  • all-nil: keep the key with a zero value (SetMapIndex with a nil interface would delete the key, as the original single-nil branch already handled);
  • one non-nil: take that value;
  • multiple non-nil: concat as before.

This makes concat order-independent for identical content and removes the unreachable panic path. toSliceValue's "type mismatch" path now only fires for genuine heterogeneous conflicts (e.g. string vs int), not for nil.

Behavior after fix:

input before after
[{"a":nil},{"a":"str"}] SIGSEGV {"a":"str"}
[{"a":"str"},{"a":nil}] err Got string, expected <nil> {"a":"str"}
[{"a":nil},{"a":nil}] SIGSEGV {"a":nil} (key preserved)
[{"a":nil},{"a":"foo"},{"a":"bar"},{"a":nil}] SIGSEGV {"a":"foobar"}
[{"a":{"x":nil}},{"a":{"x":"v"}}] nested SIGSEGV {"a":{"x":"v"}}

All existing internal/concat_test.go cases still pass; 6 new sub-tests cover both orderings plus nested-map and all-nil variants.

zh(optional):
internal.ConcatItems 是流式分片合并的统一入口,被 schema.ConcatMessagesschema.ConcatAgenticMessages(内容块 Extra)以及图状态合并共同调用。当合并 map[string]any 分片时,若同一 key 在较早分片中为 nil、在较晚分片中为非 nil 值,会触发 SIGSEGV(栈上无 recover,整个进程崩溃);反向顺序则返回误导性错误 unexpected slice element type. Got string, expected <nil>,拒绝一个本可正常合并的流。即:对同一内容合并,结果纯粹取决于分片顺序。

根因: concatMaps 会把每个 key 的全部值(含 nil)累积进 rms[key],整体交给 toSliceValuereflect.TypeOf(nil) 返回 nil reflect.Typereflect.SliceOf(nil) 随即 SIGSEGV;末位 nil 则命中 typ != vt 报类型不匹配错误。

修复: 在 concatMaps 调用 toSliceValue 之前过滤掉 nil(语义: nil 表示该 key 在该分片中缺席、不参与合并)。随后:

  • 全 nil: 以零值保留该 key(用 nil interface 调 SetMapIndex 会删 key,原单 nil 分支已如此处理);
  • 单个非 nil: 取该值;
  • 多个非 nil: 按原逻辑合并。

由此合并对相同内容与顺序无关,并消除不可达的 panic 路径。toSliceValue 的"类型不匹配"错误只对真正的异型冲突(如 string vs int)触发,不再因 nil 误报。internal/concat_test.go 既有用例全部通过,新增 6 个子测试覆盖两种顺序与嵌套 map、全 nil 变体。

(Optional) Which issue(s) this PR fixes:

Fixes #1181

(optional) The PR that updates user documentation:

N/A: internal bug fix, no public API change.

ConcatItems panics with SIGSEGV when merging map[string]any stream chunks
in which the same key is nil in an earlier chunk and set in a later chunk.
The reverse ordering returns a misleading type-mismatch error instead.
Both make concat order-dependent for the same stream content.

concatMaps collects every value (including nil) into rms[key] and hands the
whole slice to toSliceValue. reflect.TypeOf(nil) yields a nil reflect.Type,
which makes reflect.SliceOf(nil) panic; a trailing nil instead triggers the
type-mismatch branch.

Filter nil values in concatMaps before toSliceValue: nil means the key is
absent in that chunk and does not contribute. All-nil keeps the key with a
zero value (SetMapIndex with nil would delete the key); one non-nil takes
that value; multiple non-nil concat as before. This makes concat order-
independent for the same content and removes the unreachable panic path.

Closes cloudwego#1181
@CLAassistant

CLAassistant commented Aug 13, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

[internal/concat] ConcatItems panics (SIGSEGV) when a map[string]any stream key is nil in an earlier chunk and set in a later chunk

2 participants