Residual from PR #592's round-3 review, deliberately deferred there so the shape of the emptiness predicate is a recorded decision rather than a reviewer's aside.
The case
isEmptyCutRow (hypaware-core/plugins-workspace/openclaw/src/projector.js) tests content.length === 0. An Anthropic stream cut between content_block_start {type:'text',text:''} and the first content_block_delta yields:
content: [{"type":"text","text":""}] mk=b8501a26
which is not length-0, so the row is emitted with a live message_index and zero information.
Why it is the same hazard class, not a lesser one
ordinalFallbackMatch keys on message_index + role + a time window and never consults content, so any assistant row at that index is a candidate; the match key only decides whether the content pass matches first. So this row can acquire another turn's native message_id exactly like the rows #592 removes.
Why it was deferred rather than fixed
Reachability is genuinely low: the window between content_block_start and the first text_delta is milliseconds, unlike the gap before content_block_start where prefill latency lives. No failing test demands it.
What a fix needs to be careful about
Round 2 argued that widening the emptiness predicate risks over-reach, citing a thinking block with empty text but a real signature, and a tool_use block with {} input, as real evidence that must keep recording. Round 3 checked both and found neither blocks a precise fix:
- a
tool_use block from content_block_start carries a real id and name
- a
thinking block's signature is unreachable here anyway: reconstructAssistantMessage handles text_delta/thinking_delta/input_json_delta and has no signature_delta case, so a signature could only arrive on content_block_start, where Anthropic sends ""
So a predicate restricted to { type: 'text', text: '' } blocks appears safe. That should be verified rather than taken from this issue, and the decision recorded, since it widens a floor that deletes rows.
Severity: low. Depends on #592 landing first (isEmptyCutRow does not exist on master until then).
Residual from PR #592's round-3 review, deliberately deferred there so the shape of the emptiness predicate is a recorded decision rather than a reviewer's aside.
The case
isEmptyCutRow(hypaware-core/plugins-workspace/openclaw/src/projector.js) testscontent.length === 0. An Anthropic stream cut betweencontent_block_start {type:'text',text:''}and the firstcontent_block_deltayields:which is not length-0, so the row is emitted with a live
message_indexand zero information.Why it is the same hazard class, not a lesser one
ordinalFallbackMatchkeys onmessage_index+role+ a time window and never consults content, so any assistant row at that index is a candidate; the match key only decides whether the content pass matches first. So this row can acquire another turn's nativemessage_idexactly like the rows #592 removes.Why it was deferred rather than fixed
Reachability is genuinely low: the window between
content_block_startand the firsttext_deltais milliseconds, unlike the gap beforecontent_block_startwhere prefill latency lives. No failing test demands it.What a fix needs to be careful about
Round 2 argued that widening the emptiness predicate risks over-reach, citing a
thinkingblock with empty text but a realsignature, and atool_useblock with{}input, as real evidence that must keep recording. Round 3 checked both and found neither blocks a precise fix:tool_useblock fromcontent_block_startcarries a realidandnamethinkingblock'ssignatureis unreachable here anyway:reconstructAssistantMessagehandlestext_delta/thinking_delta/input_json_deltaand has nosignature_deltacase, so a signature could only arrive oncontent_block_start, where Anthropic sends""So a predicate restricted to
{ type: 'text', text: '' }blocks appears safe. That should be verified rather than taken from this issue, and the decision recorded, since it widens a floor that deletes rows.Severity: low. Depends on #592 landing first (
isEmptyCutRowdoes not exist on master until then).