CedarCopilot wants to merge 0 commits into staging from fix/destroyed-editor-on-editor-created
Live on prod, no production signal yet
Strict CloudWatch fallback saw 2,254 success-shaped log lines matching email, emails, gmail, mail, inbox, but no tied operation was present, so Libra is not calling this working.
single_user
single_user
single_user
Libra has verdicts on 0 of 1 tracked behaviors on prod; 1 is still being checked. Libra attributed 3 failures to this change at medium confidence and is still confirming them. Libra checks hourly for 3 days after each deploy.
Four users hit the route error boundary , the full-page "Something went wrong!" , today: Zach Moskow (concentrate.ai) at 19:47/19:52 UTC, Trey Layton (Pax AI) at 20:13, plus Keenan Worthington (Pirros) and Sam MacGregor (Simcare) earlier.
Every event is the same exception, Cannot read properties of null (reading 'commands'), with resolved frames onEditorCreated → TipTap's get commands, on /agent?conversationId=…&chat=… and /home?chat=… URLs.
MarkdownEditor announces its editor to the parent from a useEffect on [editor], gated by if (editor) , markdown-editor.tsx:236. That is exactly the half-guard 0bcd5cde0 was about: TipTap builds the editor a tick after mount and destroys it on a 1ms timer after unmount, and a destroyed editor is not null , it keeps its identity while commandManager/view/state are nulled underneath it. A surface that disappears in that gap leaves the effect holding a live-looking editor, and every consumer callback reaches for .commands or .getMarkdown() first thing. Because the throw lands inside an effect, React hands it to the route error boundary and the whole page is replaced.
0bcd5cde0 guarded the effect sites that touch an editor and shipped with today's 19:40 UTC deploy , verified live, read-only-markdown-view now compiles to liveEditor(editor)?.commands.setContent(…). It left the handoff itself unguarded, so the deployed bundle (release 7ddb16f8) still carries two unguarded callbacks in the ConversationView chunk:
NextStepsCard , editor.commands.setContent(nextSteps, …) (the byte at line 31, col 3339 of the live ConversationView-Ccl09jSG.js, i.e. the exact throwing site)UniversalComposer , setContent / getMarkdownZach's and Trey's errors are on ConversationView-Ccl09jSG.js, the post-deploy build. This path was still open.
Why it started. The seeding code is unchanged since 2026-04-23, the handoff since 2026-03-29, and TipTap's destroy machinery is byte-identical between 3.18.0 and 3.26.1 (I diffed them , the June upgrade is not the cause). What changed is where the card lives: b6ded45e9 put NextStepsCard on the Overview tab (2026-07-13) and 47dc63f32 gave that tab doc embeds (2026-07-27), so an artifact now slides over it and unmounts it mid-creation. The first page-replacing occurrence is 2026-07-29 17:00 UTC, on a /strategicOverview/ URL.
One guard, at the handoff rather than at the seven callbacks that consume it:
const live = liveEditor(editor);
if (live) onEditorCreated?.(live);
Guarding here means no consumer can ever be handed a dead editor , including the four outside the conversation view (AgentsEditor, the share page, the agents demo) that would each otherwise need the same line. Nothing is lost by skipping the call: a destroyed editor cannot accept content, and a real remount builds a new editor, which changes the [editor] dep and re-runs the effect to seed normally.
apps/mail/tests/components/markdown-editor-liveness.test.tsx drives useEditor directly so the destroyed state is exact rather than timing-dependent. Three cases: a destroyed editor is never announced, a live one still is and seeding works, and null announces nothing.
pnpm --filter @zero/mail exec jest tests/components/markdown-editor-liveness.test.tsx
Verified both ways , the first case fails on the old if (editor) line with the production error text, and all three pass with the guard. types:test is clean for both touched files (the remaining errors are pre-existing on staging); eslint is clean.
After deploy, the check is that the Cannot read properties of null (reading 'commands') issue stops taking new events with an onEditorCreated frame.
🤖 Fixed by Sherlock
The
Libra has not measured any production surfaces for this change yet.