Skip to content

Commit e7062c2

Browse files
committed
🤖 Fix init display bug - restore defensive checks
The cleanup removed necessary defensive checks, causing crashes when init-output or init-end arrive without init-start (can happen during replay or out-of-order events). Restored graceful handling. Added TDD test to prevent regression. Generated with `cmux`
1 parent 8c8a92a commit e7062c2

File tree

2 files changed

+74
-3
lines changed

2 files changed

+74
-3
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { StreamingMessageAggregator } from "./StreamingMessageAggregator";
2+
3+
describe("Init display after cleanup changes", () => {
4+
it("should display init messages correctly", () => {
5+
const aggregator = new StreamingMessageAggregator();
6+
7+
// Simulate init start
8+
aggregator.handleMessage({
9+
type: "init-start",
10+
hookPath: "/test/.cmux/init",
11+
timestamp: Date.now(),
12+
});
13+
14+
let messages = aggregator.getDisplayedMessages();
15+
expect(messages).toHaveLength(1);
16+
expect(messages[0].type).toBe("workspace-init");
17+
expect((messages[0] as any).status).toBe("running");
18+
19+
// Simulate init output
20+
aggregator.handleMessage({
21+
type: "init-output",
22+
line: "Installing dependencies...",
23+
timestamp: Date.now(),
24+
isError: false,
25+
});
26+
27+
messages = aggregator.getDisplayedMessages();
28+
expect(messages).toHaveLength(1);
29+
expect((messages[0] as any).lines).toContain("Installing dependencies...");
30+
31+
// Simulate init end
32+
aggregator.handleMessage({
33+
type: "init-end",
34+
exitCode: 0,
35+
timestamp: Date.now(),
36+
});
37+
38+
messages = aggregator.getDisplayedMessages();
39+
expect(messages).toHaveLength(1);
40+
expect((messages[0] as any).status).toBe("success");
41+
expect((messages[0] as any).exitCode).toBe(0);
42+
});
43+
44+
it("should handle init-output without init-start (defensive)", () => {
45+
const aggregator = new StreamingMessageAggregator();
46+
47+
// This might crash with non-null assertion if initState is null
48+
expect(() => {
49+
aggregator.handleMessage({
50+
type: "init-output",
51+
line: "Some output",
52+
timestamp: Date.now(),
53+
isError: false,
54+
});
55+
}).not.toThrow();
56+
});
57+
58+
it("should handle init-end without init-start (defensive)", () => {
59+
const aggregator = new StreamingMessageAggregator();
60+
61+
expect(() => {
62+
aggregator.handleMessage({
63+
type: "init-end",
64+
exitCode: 0,
65+
timestamp: Date.now(),
66+
});
67+
}).not.toThrow();
68+
});
69+
});

src/utils/messages/StreamingMessageAggregator.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -511,15 +511,17 @@ export class StreamingMessageAggregator {
511511
}
512512

513513
if (isInitOutput(data)) {
514+
if (!this.initState) return; // Defensive: shouldn't happen but handle gracefully
514515
const line = data.isError ? `ERROR: ${data.line}` : data.line;
515-
this.initState!.lines.push(line.trimEnd());
516+
this.initState.lines.push(line.trimEnd());
516517
this.invalidateCache();
517518
return;
518519
}
519520

520521
if (isInitEnd(data)) {
521-
this.initState!.exitCode = data.exitCode;
522-
this.initState!.status = data.exitCode === 0 ? "success" : "error";
522+
if (!this.initState) return; // Defensive: shouldn't happen but handle gracefully
523+
this.initState.exitCode = data.exitCode;
524+
this.initState.status = data.exitCode === 0 ? "success" : "error";
523525
this.invalidateCache();
524526
return;
525527
}

0 commit comments

Comments
 (0)