fix(cursor): pin native composer conversation id to prompt_cache_key#367
fix(cursor): pin native composer conversation id to prompt_cache_key#367kimrinking-cell wants to merge 1 commit into
Conversation
Codex Desktop often replays full history under store:false without a usable previous_response_id restore, so each turn minted a fresh cursor_* id and Composer 2.5 never hit Cursor cache/context carry-forward. Co-authored-by: Cursor <cursoragent@cursor.com>
📝 WalkthroughWalkthroughCursor conversation-ID selection is centralized, native models can derive stable IDs from prompt-cache keys, and adapter state synchronization is adjusted. Tests cover fresh tool-result continuations and ChangesCursor conversation continuity
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Adapter as createCursorAdapter.runTurn
participant Builder as createCursorRequest
participant Resolver as resolveCursorConversationId
participant Cursor as Cursor transport
Adapter->>Builder: Build request with parsed state and model
Builder->>Resolver: Resolve conversationId
Resolver-->>Builder: Fresh, remembered, or prompt-cache-derived ID
Builder-->>Adapter: Return request and conversationId
Adapter->>Cursor: Execute request
Adapter->>Adapter: Persist and rekey conversation context when required
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 24d2ac04b7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // previous_response_id still share one Cursor conversation (cache + checkpoints). | ||
| if (isCursorNativeWireModel(wireModelId)) { | ||
| const key = parsed.options.promptCacheKey?.trim(); | ||
| if (key) return stableCursorConversationIdFromPromptCacheKey(key); |
There was a problem hiding this comment.
Avoid pinning Cursor conversations to shared cache keys
On the Claude /v1/messages path when metadata.user_id is absent (Claude Desktop), the translator still creates prompt_cache_key from only the model/system/tools cache cohort, and src/server/claude-messages.ts:631-638 explicitly treats that fallback as shared across Desktop conversations rather than a session id. Returning a Cursor conversation id for any non-empty promptCacheKey here means unrelated Desktop chats routed to native Cursor models (composer-*/default) can send the same cursor_<hash> and resume/share Cursor server conversation state instead of only cache affinity. Please carry cache-key provenance through parsing or otherwise skip synthesized shared keys before using them as conversation identity.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/adapters/cursor/request-builder.ts`:
- Around line 174-180: The stableCursorConversationIdFromPromptCacheKey flow
incorrectly derives shared conversation state from promptCacheKey. Carry a
distinct upstream conversation/thread identifier through request construction,
derive the provider/client-scoped Cursor ID from that identifier, and preserve
fresh-ID behavior when it is absent; remove promptCacheKey as the conversation
identity source. Update cursor request-builder tests to verify distinct
conversations with the same cache key remain isolated.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: e83541e8-5fa3-4c27-bd1f-9a6de354e6b8
📒 Files selected for processing (4)
src/adapters/cursor.tssrc/adapters/cursor/request-builder.tstests/cursor-request-builder.test.tstests/server-combo-failover-e2e.test.ts
| export function stableCursorConversationIdFromPromptCacheKey(promptCacheKey: string): string { | ||
| const digest = createHash("sha256") | ||
| .update("ocx-cursor-conv:") | ||
| .update(promptCacheKey) | ||
| .digest("hex") | ||
| .slice(0, 32); | ||
| return `cursor_${digest}`; |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Do not treat prompt_cache_key as a conversation ID.
Lines 174-180 deterministically map every native request sharing a cache key to the same Cursor conversation. prompt_cache_key is documented for cache bucketing of similar requests, not as a unique conversation identifier; its predecessor semantics describe a stable end-user identifier. (platform.openai.com) Two independent store:false conversations for the same user/key can therefore inherit each other’s Cursor context/checkpoints.
Carry a distinct, upstream conversation/thread identifier through the request and derive the Cursor ID from that (scoped to the provider/client). When no such identifier exists, retain fresh-ID behavior rather than persisting conversation state from a cache key. Update tests/cursor-request-builder.test.ts Lines 41-69 to cover distinct conversations sharing a cache key.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/adapters/cursor/request-builder.ts` around lines 174 - 180, The
stableCursorConversationIdFromPromptCacheKey flow incorrectly derives shared
conversation state from promptCacheKey. Carry a distinct upstream
conversation/thread identifier through request construction, derive the
provider/client-scoped Cursor ID from that identifier, and preserve fresh-ID
behavior when it is absent; remove promptCacheKey as the conversation identity
source. Update cursor request-builder tests to verify distinct conversations
with the same cache key remain isolated.
|
Closing this in favor of #366. Thanks for surfacing this — the continuity diagnosis is right. We're already chasing the same class of failure on external Cursor models ( We'll cover that path in #366 (stable provider conversation identity for external models as well), and we'll thank / mention you in the changelog for pointing at the continuity root cause. |
Keep native Composer and external models on one Cursor conversation across store:false / tool continuations so Resume and context carry-forward can hit again. Bound external root replay to cut late invalid_argument after hydration. Thanks @kimrinking-cell for the continuity diagnosis in #367.
Keep native Composer and external models on one Cursor conversation across store:false / tool continuations so Resume and context carry-forward can hit again. Bound external root replay to cut late invalid_argument after hydration. Thanks @kimrinking-cell for the continuity diagnosis in #367.
Summary
composer-2.5) now derive a stable conversation id from Codexprompt_cache_keywhen_cursorConversationIdis not restored fromprevious_response_id.cursor_*id on everystore:falsefull-history turn, so Cursor prompt-cache / context-usage carry-forward can hit again.Verification
bun run typecheckbun test tests/cursor-request-builder.test.ts tests/cursor-adapter.test.ts tests/server-combo-failover-e2e.test.ts tests/responses-state.test.ts(91 pass)bun run privacy:scanprompt_cache_key→ same conversation idChecklist
Made with Cursor
Summary by CodeRabbit
Bug Fixes
Tests