macOS desktop: completed empty final hides the preceding non-empty final in the same turn
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
macOS desktop: completed empty final hides the preceding non-empty final in the same turn
Environment
- ChatGPT/Codex desktop for macOS, version 26.901.51231, build 8109.
- Bundled Codex CLI: 0.153.4.
- Official application signature and ASAR header integrity both validate.
Observed behavior
A complete parent assistant response appeared in the desktop conversation, then disappeared after a late follow-up event and a second, empty final answer within the same turn. Both final messages remain in the persisted transcript. This report does not include private prompts, actual response text, thread identifiers, account data, or raw logs.
The frontend grouping module deterministically reproduces the display divergence: appending a completed empty final selects that empty message as assistantItem and moves the preceding non-empty final into agentItems. The main turn renderer sends those agent items to the collapsible activity area, while the standalone final reply receives the empty object.
Minimal normalized input
const full = {
id: 'full',
type: 'assistant-message',
content: 'Example response',
phase: 'final_answer',
completed: true,
};
const empty = { ...full, id: 'empty', content: '' };
// After the module's existing initialization:
splitItemsIntoRenderGroups([full], 'completed');
// assistantItem.id === 'full'
splitItemsIntoRenderGroups([full, empty], 'completed');
// Actual: assistantItem.id === 'empty'; agentItems includes full.
// Expected: the existing non-empty final stays available as the final response.
This is a deterministic frontend module reproduction. The original desktop incident was observed once; the complete app-server and installed GUI event lifecycle has not been loop-tested.
Local diagnosis and candidate
Affected installed asset: split-items-into-render-groups-57548326ecf1.js.
The function computes whether the selected assistant item has non-whitespace content or structured output, but does not use that result to reject the empty selected final. Four observed callers share this function.
A local candidate adds one guard in the shared grouping loop, after the existing specific item classifications and immediately before collecting general activity items. It skips a message only when all of these hold:
type === 'assistant-message'phase === 'final_answer'completed === truedelivery !== 'async'(content?.trim().length ?? 0) === 0- no truthy
structuredOutput
The candidate leaves incomplete/streaming messages, async delivery, commentary, non-empty finals, and structured output—including {}—unchanged. It changes display grouping only, not persisted transcript data. The same-turn event producer should also be investigated separately; this guard is a frontend resilience fix, not a claim to prevent all empty output generation.
Validation and limits
The original and candidate compiled modules were executed in isolated Chromium with the unchanged dependencies loaded read-only from the installed application. Dependencies were checked against their existing ASAR integrity hashes. No replacement selector or dependency stubs were used.
22 cases passed, including the incident's two-final slice, unchanged normal/control cases, whitespace and repeated empty finals, structured output, streaming and async messages, and preservation of complete grouping results. An independent code review reran all 22 cases successfully.
The signed installed application was not modified, re-signed, or restarted. Installed GUI and restart validation remain outstanding. The local verification archive contains private incident material and is deliberately not attached to this public report.
Related reports
- #28751 reports empty messages persisted while resuming older Windows threads. Here both the non-empty and subsequent empty final remain persisted.
- #39885 reports completed replies hidden by stale/interrupted state after a Windows/SSH restart. This occurrence happened live, within the same turn, without restarting.
Please consider a shared grouping guard for completed empty finals and investigate why the same turn may emit another empty final after a visible completed response.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with split-items-into-render-groups-57548326ecf1.js and reproduce the two-message case in the issue, then inspect the shared grouping loop and its four callers. Run the isolated Chromium validation covering the 22 listed cases; done means the preceding non-empty final remains the standalone response while streaming, async, structured-output, and normal grouping cases stay unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- desktop, frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100