Skip to content

fix: five correctness bugs (model-wipe, infinite retry loop, type gap) - #260

Closed
dylanneve1 wants to merge 1 commit into
mainfrom
claude/eager-sagan-yHlUz
Closed

dylanneve1 wants to merge 1 commit into
mainfrom
claude/eager-sagan-yHlUz

Conversation

@dylanneve1

Copy link
Copy Markdown
Owner

Summary

Deep code review surfaced five bugs, three of them confirmed critical through independent verification.

1. CRITICAL β€” fallback_model path permanently wipes modelByBackend (3 files)

Files: src/backend/shared/handle-retry.ts, src/backend/openai-agents/handler.ts, src/backend/codex/handler.ts

After migrateLegacyModelField runs at startup, getChatSettings(chatId).model is undefined (the legacy field is deleted). The fallback_model retry path saved this as originalModel, then the finally block called setChatModel(chatId, undefined). Per chat-settings.ts line 346–354, passing undefined to setChatModel is the "reset everything" semantic: it deletes both modelByBackend and model, permanently wiping every per-backend model the user had selected.

Fix: Pass the fallback model via params.model instead of mutating chat settings. The swap is now purely in-memory β€” no setChatModel calls, no try/finally, and user preferences survive the retry untouched.

2. CRITICAL β€” openai-agents flow-violation check causes infinite retry loop

File: src/backend/openai-agents/handler.ts

detectFlowViolation was called with retried: _retried (boolean) but without retryCount or maxRetries. With the new retryCount-based API in flow-violation.ts, retried: true maps to retryCount = inputs.retryCount ?? 1. Since 1 < FLOW_VIOLATION_MAX_RETRIES (3) is always true, shouldRetry never becomes false. On a model that persistently violates the flow contract, the handler recurses indefinitely.

Fix: Explicitly pass retryCount: (_retried ? 1 : 0) and maxRetries: 1, preserving the single-pass behaviour documented in the handler's own comment. Also adds the missing toolCalls: streamState.toolCalls parameter so tool-call-only violations (model calls tools but no terminator) are detected correctly.

3. MEDIUM β€” openai-agents flow-violation log always says "trailing prose"

File: src/backend/openai-agents/handler.ts

The log line hardcoded "trailing prose (N chars)" even when the violation was caused by tool calls without a terminator. Now uses violation.reason, which is already correctly phrased for both cases by flow-violation.ts.

4. MEDIUM β€” RemoteAssistantInfo missing id field; dedup cast was untyped

File: src/backend/remote-server/session-helpers.ts

listSessionMessages deduplicates messages by info.id, but RemoteAssistantInfo didn't declare an id field. The working dedup relied on an ad-hoc double-cast (message as Record<string, unknown>)?.info as { id?: string } that TypeScript couldn't validate. A future refactor could silently break the cast without any type error.

Fix: Add id?: string to RemoteAssistantInfo and simplify the dedup cast to the typed (message as { info?: RemoteAssistantInfo })?.info?.id.

5. PLAUSIBLE β€” events.ts scope filter uses truthiness instead of !== undefined

File: src/backend/remote-server/events.ts

if (evtSessionID && evtSessionID !== ctx.sessionId) treats sessionID: "" (empty string) as "no session scope", allowing a session.turn.close with a blank sessionID to prematurely stop any session's SSE loop. The defensive fix uses !== undefined so only a truly absent sessionID is treated as unscoped.


Test plan

  • npm test passes (2997 tests, 0 failures) βœ… verified locally
  • shared-handle-retry.test.ts β€” updated two test cases to assert the new correct behaviour (fallback model injected via params.model, chat settings untouched)
  • Manually verify model picker persists across a rate-limit fallback by checking getChatSettings after a fallback_model retry β€” modelByBackend should be unchanged
  • Verify flow-violation retry in openai-agents backend stops after exactly one reminder (previously would loop indefinitely on a stubborn model)

https://claude.ai/code/session_01JpQ53zUQ5RpZq16mEH1Tgj


Generated by Claude Code

1. fallback_model path wipes modelByBackend (CRITICAL)
   getChatSettings(chatId).model is undefined after migrateLegacyModelField
   runs, so the finally-block restore called setChatModel(chatId, undefined),
   permanently deleting every per-backend model override the user had set.
   Fix: pass the fallback model via params.model instead of mutating chat
   settings at all. Applied to handle-retry.ts (shared helper), the inline
   copy in openai-agents/handler.ts, and codex maybeFallbackForChatGptMismatch.

2. openai-agents flow-violation infinite retry loop
   detectFlowViolation was called without retryCount or maxRetries. With the
   new retryCount-based API, retried:true maps to retryCount=1 which is always
   < FLOW_VIOLATION_MAX_RETRIES=3, so shouldRetry is permanently true and the
   handler recurses indefinitely on persistent violators. Fix: pass
   retryCount:(_retried?1:0) and maxRetries:1 to cap at a single retry (the
   documented single-pass behaviour). Also adds the missing toolCalls field so
   tool-call-only violations are detected.

3. openai-agents flow-violation log uses wrong format string
   The log line hardcoded "trailing prose (N chars)" even when the violation
   was caused by tool calls with no terminator. Fix: use violation.reason.

4. RemoteAssistantInfo missing id field / untyped dedup cast
   The listSessionMessages dedup cast (message as ...).info as {id?:string}
   was untyped and fragile. Fix: add id?:string to RemoteAssistantInfo and
   use the typed form (message as {info?:RemoteAssistantInfo}).info?.id.

5. events.ts scope filter uses truthiness instead of !== undefined
   if (evtSessionID && ...) treats sessionID="" as "no scope tag", allowing
   a session.turn.close with a blank sessionID to stop any session's loop.
   Fix: use evtSessionID !== undefined for the guard.

https://claude.ai/code/session_01JpQ53zUQ5RpZq16mEH1Tgj
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants