fix(orchestration): validate state.json before it reaches the renderer; add error boundary - #93
Merged
amirlehmam merged 2 commits intoJul 13, 2026
Conversation
…r; add error boundary A malformed wmux-orch-*/state.json blanked the entire window. The watcher read the file with `JSON.parse(raw) as OrchestrationState` — a cast, not a check — and broadcast it to the renderer every second. A run written with `orchestrationId` instead of `id` therefore reached OrchestrationPanel, which does `orch.id.replace(/^orch-/, '')` and threw. With no error boundary anywhere in the tree, React unmounted the whole app, leaving a window painted in the background colour that only a restart cleared. The watcher prefers runs with status "running", so a single stale file reproduced it on every launch. state.json is written by a separate process, so treat it as untrusted: - orchestration-watcher: shape-check id/status/waves before broadcasting; ignore (and warn once per dir) anything that fails. - ErrorBoundary: new component; wraps the root as a last-resort net and the orchestration panel so a bad payload degrades to a broken panel, not a dead window. - OrchestrationPanel: bail out on a run with no id/waves rather than throw.
…ot 495520:02:09 parseIso() returns 0 for a missing or unparseable startedAt, and the panel then rendered `now - 0` — the entire Unix epoch — as the elapsed time. A run ten minutes old displayed "495520:02:09" (~56 years). An unknown start time is not a very old run. Render an em dash instead. The plugin's schema names the field `startedAt` (skills/orchestrate/SKILL.md, spawn-agents.sh, json-tool.js); a run that writes some other key for its start time lands in exactly this path.
Owner
|
Merged — thanks for another surgical fix. The analysis in #92 was spot on: the watcher trusting a cross-process file and the renderer having no error boundary were two separate holes, and this closes both. The |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #92 — a malformed
wmux-orch-*/state.jsonblanks the entire window.The bug
orchestration-watcher.tsreads state.json with a cast, not a check:state.json is written by a separate process (the plugin, or an agent hand-rolling it), so it is untrusted input. A run written with
"orchestrationId"instead of"id"reachesOrchestrationPanel, which doesorch.id.replace(/^orch-/, "")and throws. With no error boundary anywhere in the renderer, React unmounts the whole tree and the window is left painted in its#1a1a1abackground — looks like a hang, cleared only by a restart. And since the watcher prefersstatus: "running", one stale file reproduces it on every launch.Repro and full analysis in #92.
The fix
orchestration-watcher— shape-checkid/status/wavesbefore broadcasting; ignore anything that fails, warning once per directory rather than once per 1s poll tick.ErrorBoundary(new) — wraps the root as a last-resort net, and the orchestration panel specifically (silent, so a bad run just hides the panel). This kills the bug class: no single data-driven panel can blank the window again. Reusable for the other panels that render data wmux does not own (session snapshots, agent metadata).OrchestrationPanel— bail out on a run with noid/wavesinstead of throwing.Verification
tests/unit/orchestration-watcher.test.tspins the exactorchestrationId-instead-of-idshape, plus missing/empty/non-stringid, missingstatus, non-arraywaves, and malformed waves.npm test→ 197 passing (26 files).tscclean. No new lint problems (35 on master, 35 on this branch).status: "running"no longer produces a console error and the window stays up; a well-formed run still renders in the sidebar as before.