Session freezes after switching model OpenAI -> DeepSeek -> OpenAI; turn lease is never released
- Dominant language
- TypeScript
- Stars
- 2.7k
- Forks
- 395
- Avg merge
- 21h 48m
- Merged PRs (30d)
- 776
Description
## Bug report
### Summary
After switching a session's model OpenAI -> DeepSeek -> OpenAI, the session becomes permanently unresponsive ("Reconnecting..." / stuck as if a turn is running). New user messages are stored but never executed, and the only reliable recovery is quitting and relaunching the app.
This also blocks the built-in feedback channel: in this affected session, the built-in MCP tools (e.g. `cindy_helper`, `cindy_memory`) are not mounted, so `submit_github_issue` is unavailable.
- **App version:** 0.1.46 (macOS, Apple Silicon M4)
- **Frequency:** Reproduced in multiple sessions; consistently reproducible with the steps below
- **Feedback category:** Bug
### Steps to reproduce
1. Start a conversation that contains at least one image attachment (e.g. a WebP photo) in its history.
2. Switch the session model to DeepSeek (`deepseek-v4-flash`).
3. Send a message. The request fails with:
```
invalid_request_error / unsupported_feature:
"Responses feature is not supported by the Chat Completions bridge: input_image"
```
4. Switch the model back to an OpenAI model (e.g. `gpt-5.6-terra`).
5. Send another message.
### Expected behavior
- Switching models should cleanly tear down the previous live session and start a fresh one.
- A failed turn (unsupported feature, etc.) should not leave the session in a stuck state.
- New messages after the switch should execute normally.
### Actual behavior
- After step 5 the message is persisted (visible in local session storage) but no agent turn ever starts.
- The session stays in a stuck state; repeated manual aborts do not recover it.
- Local state shows a turn lease that is still held: `active_turn_started_at` is set while `active_turn_pid` is empty, so nothing is actually running.
- The UI repeatedly shows "Reconnecting... 1/5".
- Only quitting/relaunching the app recovers the session.
### Sanitized diagnostics (no credentials, tokens, emails, or personal paths)
From app logs for the affected session:
1. Model switch tears down the live session (expected), then a lazy re-create is issued:
`set-model: closed live session after credential mode switch`
`send: lazy create-session`
2. The DeepSeek turn fails immediately:
`codex turn error` -> `invalid_request_error / unsupported_feature / input_image`, `willRetry: false`
3. After switching back to OpenAI, session storage still references the old SDK session id while the UI shows the new model, and the re-created session does not process subsequent turns.
4. The final user message is written to the messages table (timestamp recorded), but no new Codex turn log line exists afterward; a manual abort logs `abort took effect; a newer turn is running — not closing session` followed by `ignoring stale abort completion`.
5. Session row: model/provider updated to the OpenAI model, status `active`, but `active_turn_started_at` remains set with an empty `active_turn_pid` and no `last_turn_ended_at`.
### Additional related issue
In the affected session the built-in MCP servers `cindy_helper` and `cindy_memory` are not mounted (querying them returns `unknown MCP server`), which makes the in-app feedback/issue submission channel unavailable. This may be the same root cause area (session teardown/re-creation not fully resetting injected tooling).
### Suggested investigation
- On model/provider switch, ensure the old turn lease/active-turn markers are released before re-creating the session.
- After a non-retryable turn error, reset the session state so the next message starts a fresh generation.
- Verify that the re-created session uses the new provider/model end-to-end (SDK session id, provider id, model), and that built-in MCP tooling is re-injected.
Contributor guide
Research direction
Start by tracing the model-switch teardown and lazy session re-creation paths, then inspect how active_turn_started_at, active_turn_pid, and session/provider identifiers are updated after a non-retryable turn error. Done means switching models and recovering from the unsupported input_image error releases the lease, uses the new provider and model, processes the next message, and re-injects the built-in MCP servers.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- ai, desktop
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100