makecindy / makecindy/cindy

Session freezes after switching model OpenAI -> DeepSeek -> OpenAI; turn lease is never released

Open
#2,693 1 comment 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.