Skip to content

fix(cursor): pin native composer conversation id to prompt_cache_key#367

Closed
kimrinking-cell wants to merge 1 commit into
lidge-jun:devfrom
kimrinking-cell:fix/cursor-composer-context-continuity
Closed

fix(cursor): pin native composer conversation id to prompt_cache_key#367
kimrinking-cell wants to merge 1 commit into
lidge-jun:devfrom
kimrinking-cell:fix/cursor-composer-context-continuity

Conversation

@kimrinking-cell

@kimrinking-cell kimrinking-cell commented Jul 24, 2026

Copy link
Copy Markdown

Summary

  • Native Cursor models (e.g. composer-2.5) now derive a stable conversation id from Codex prompt_cache_key when _cursorConversationId is not restored from previous_response_id.
  • Stops minting a fresh cursor_* id on every store:false full-history turn, so Cursor prompt-cache / context-usage carry-forward can hit again.
  • External-model tool-result force-fresh behavior is unchanged; the adapter no longer pre-mints a random id that would hide the cache-key pin.

Verification

  • bun run typecheck
  • bun 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:scan
  • Live proxy restarted with the patched files; unit probe confirmed same prompt_cache_key → same conversation id

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed. (internal continuity only — no user-doc change)
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Made with Cursor

Summary by CodeRabbit

  • Bug Fixes

    • Improved Cursor conversation continuity across follow-up requests.
    • Preserved conversation context for native Composer models using the same prompt cache key.
    • Ensured tool-result continuations start fresh conversations when required.
    • Improved continuity for streamed and non-stored conversation flows.
  • Tests

    • Added coverage for stable conversation reuse and fresh conversations after external tool results.
    • Added end-to-end checks for conversation continuity across multiple request patterns.

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>
@github-actions github-actions Bot added the bug Something isn't working label Jul 24, 2026
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Cursor 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 store:false conversation continuity.

Changes

Cursor conversation continuity

Layer / File(s) Summary
Conversation-ID resolution
src/adapters/cursor/request-builder.ts
Adds deterministic cursor_ IDs derived from SHA-256 prompt-cache keys and centralizes fresh, remembered, native-model, and generated-ID selection.
Adapter conversation synchronization
src/adapters/cursor.ts
runTurn builds requests before updating _cursorConversationId and only rekeys context when a prior ID exists and the request produces a different ID.
Conversation continuity validation
tests/cursor-request-builder.test.ts, tests/server-combo-failover-e2e.test.ts
Tests stable prompt-cache IDs, fresh external tool-result IDs, and composer-2.5 store:false continuation reuse.

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
Loading

Possibly related PRs

Suggested reviewers: lidge-jun, wibias, ingwannu

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: pinning native Composer conversation IDs to prompt_cache_key.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5157c49 and 24d2ac0.

📒 Files selected for processing (4)
  • src/adapters/cursor.ts
  • src/adapters/cursor/request-builder.ts
  • tests/cursor-request-builder.test.ts
  • tests/server-combo-failover-e2e.test.ts

Comment on lines +174 to +180
export function stableCursorConversationIdFromPromptCacheKey(promptCacheKey: string): string {
const digest = createHash("sha256")
.update("ocx-cursor-conv:")
.update(promptCacheKey)
.digest("hex")
.slice(0, 32);
return `cursor_${digest}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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.

@Wibias

Wibias commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

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 (gpt-5.6-sol / similar) in #366: under store:false full-history turns, minting a fresh cursor_* id each time breaks Resume / context carry-forward and shows up as intermittent Connect invalid_argument after hydration (usedTokens: 0). So the problem isn't limited to native Composer.

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.

@Wibias Wibias closed this Jul 24, 2026
Wibias added a commit that referenced this pull request Jul 24, 2026
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.
Wibias added a commit that referenced this pull request Jul 24, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants