Skip to content

Commit 133adb8

Browse files
authored
🤖 fix: validate mode at stats schema boundaries (#1248)
MuxMetadata.mode is typed as `string` (for future custom modes), but the stats Zod schemas only accept `"plan" | "exec" | undefined`. The code was casting mode without validation, which TypeScript allowed but Zod rejected at runtime. This caused ZodError spam on every stream delta event, since each delta triggers `getSnapshot()` which parses the schema. **Fix:** Validate mode before storing/emitting - treat unknown modes as undefined for stats purposes. --- _Generated with `mux` • Model: `anthropic:claude-opus-4-5` • Thinking: `high`_
1 parent d246709 commit 133adb8

File tree

3 files changed

+13
-4
lines changed

3 files changed

+13
-4
lines changed

src/common/types/message.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ export interface MuxMetadata {
124124
// Readers should use helper: isCompacted = compacted !== undefined && compacted !== false
125125
compacted?: "user" | "idle" | boolean;
126126
toolPolicy?: ToolPolicy; // Tool policy active when this message was sent (user messages only)
127-
mode?: string; // The mode (plan/exec/etc) active when this message was sent (assistant messages only)
127+
mode?: string; // The mode active when this message was sent (assistant messages only) - plan/exec today, custom modes in future
128128
cmuxMetadata?: MuxFrontendMetadata; // Frontend-defined metadata, backend treats as black-box
129129
muxMetadata?: MuxFrontendMetadata; // Frontend-defined metadata, backend treats as black-box
130130
}

src/node/services/sessionTimingService.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,11 +482,15 @@ export class SessionTimingService {
482482

483483
const model = normalizeGatewayModel(data.model);
484484

485+
// Validate mode: stats schema only accepts "plan" | "exec" for now.
486+
// Custom modes will need schema updates when supported.
487+
const mode = data.mode === "plan" || data.mode === "exec" ? data.mode : undefined;
488+
485489
const state: ActiveStreamState = {
486490
workspaceId: data.workspaceId,
487491
messageId: data.messageId,
488492
model,
489-
mode: data.mode,
493+
mode,
490494
startTimeMs: data.startTime,
491495
firstTokenTimeMs: null,
492496
completedToolExecutionMs: 0,

src/node/services/streamManager.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -865,7 +865,9 @@ export class StreamManager extends EventEmitter {
865865
streamInfo.state = StreamState.STREAMING;
866866

867867
// Emit stream start event (include mode from initialMetadata if available)
868-
const streamStartMode = streamInfo.initialMetadata?.mode as "plan" | "exec" | undefined;
868+
// Validate mode - stats schema only accepts "plan" | "exec" for now
869+
const rawMode = streamInfo.initialMetadata?.mode;
870+
const streamStartMode = rawMode === "plan" || rawMode === "exec" ? rawMode : undefined;
869871
this.emit("stream-start", {
870872
type: "stream-start",
871873
workspaceId: workspaceId as string,
@@ -1776,7 +1778,10 @@ export class StreamManager extends EventEmitter {
17761778
await this.tokenTracker.setModel(streamInfo.model);
17771779

17781780
// Emit stream-start event (include mode from initialMetadata if available)
1779-
const replayMode = streamInfo.initialMetadata?.mode as "plan" | "exec" | undefined;
1781+
// Validate mode - stats schema only accepts "plan" | "exec" for now
1782+
const rawReplayMode = streamInfo.initialMetadata?.mode;
1783+
const replayMode =
1784+
rawReplayMode === "plan" || rawReplayMode === "exec" ? rawReplayMode : undefined;
17801785
this.emit("stream-start", {
17811786
type: "stream-start",
17821787
workspaceId,

0 commit comments

Comments
 (0)