Skip to content

fix(claude): stabilize Responses instructions for Muse/Go prompt cache - #2

Closed
Warexpor wants to merge 7 commits into
devfrom
cursor/feat-claude-instructions-cache-stabilize-915b
Closed

Warexpor wants to merge 7 commits into
devfrom
cursor/feat-claude-instructions-cache-stabilize-915b

Conversation

@Warexpor

@Warexpor Warexpor commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • Claude Code through OpenCodex puts system text into OpenAI Responses instructions. Growing <total_tokens>N tokens left</total_tokens> / TaskCreate footers churn that prefix and collapse Muse/Go prompt-cache hit rates.
  • Relocation is opt-in at the translator: stabilizePromptCache?: boolean defaults to false. Ordinary Anthropic callers with that exact unfenced suffix keep instructions and input unchanged. The Claude Code /v1/messages inbound path passes true. This is not inferred from metadata.user_id.
  • Matcher stays the real harness shape (TRAILING_TOTAL_RE for <total_tokens>\d+\s+tokens left</total_tokens>, exact TaskCreate paragraph, trailing unfenced only, unclosed fence through EOF). No match returns the original string byte-for-byte.
  • Desktop prompt_cache_key fallback hashes raw systemParts outside opt-in (previous key). When opted in, that fallback hashes the same string as body.instructions after stabilize.

SOCKS5 / Go session-header work is intentionally out of scope. Related Anthropic thinking-replay issue lidge-jun#3719 stays open (link only).

Upstream review PR: lidge-jun#4052

Verification

  • bun test tests/claude-integration/claude-inbound-cache-stabilize.test.ts tests/claude-integration/claude-inbound.test.ts75 pass
  • bun run typecheck — pass

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.
Open in Web Open in Cursor 

Strip growing <total_tokens> and TaskCreate footers from Claude Code
system text before it becomes Responses instructions, and reattach the
latest notice as a trailing input message so Muse/Go prefix cache can
hit. Paper and measured rates live under docs/claude-instructions-cache-stabilize/.

Co-authored-by: Warexpor <warexpor@gmail.com>
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Sep 8, 2026
cursoragent and others added 6 commits September 8, 2026 18:15
The Claude Code path still uses metadata.user_id. Desktop has no session
id, so the fallback key now fingerprints the cacheable prefix instead of
raw systemParts that still contain <total_tokens> / TaskCreate footers.

Co-authored-by: Warexpor <warexpor@gmail.com>
Restrict strip to standalone integer <total_tokens> footer lines and
the exact Claude Code reminder paragraph so inline docs of those tags
or TaskCreate stay in instructions.

Co-authored-by: Warexpor <warexpor@gmail.com>
Identify Claude Code <total_tokens> / TaskCreate notices as a trailing
suffix outside markdown fences. No match returns the original system
text byte-for-byte so fenced examples and whitespace-only prompts stay
untouched. Translator regressions cover those no-ops.

Co-authored-by: Warexpor <warexpor@gmail.com>
Relocation runs only when metadata.user_id identifies Claude Code.
Unmatched fence openers cover through EOF so an unfinished fenced
example plus a numeric total_tokens line is not a harness suffix.
Desktop keeps the raw systemParts cache-key fallback.

Co-authored-by: Warexpor <warexpor@gmail.com>
Claude Code writes <total_tokens>N tokens left</total_tokens>, not a
bare integer. Digit-only matching left the harness suffix in
instructions and collapsed Muse/Go hit rate to baseline. Match that
shape as a trailing unfenced suffix, run peel on every inbound
translation (no-op when absent), and hash Desktop prompt_cache_key from
the same string used for instructions.

Co-authored-by: Warexpor <warexpor@gmail.com>
Keep the real N tokens left matcher and unclosed-fence-to-EOF handling.
Relocation is now stabilizePromptCache (default false) on the translator,
not metadata.user_id. Ordinary callers keep a matching suffix in
instructions; /v1/messages passes true. Desktop prompt_cache_key hashes
raw systemParts outside opt-in and the instructions string when opted in.

Co-authored-by: Warexpor <warexpor@gmail.com>
@cursor

cursor Bot commented Sep 11, 2026

Copy link
Copy Markdown

@Ingwannu addressed on 0847e21.

Kept the real N tokens left matcher, TaskCreate paragraph, trailing-unfenced-only peel, and unclosed-fence-to-EOF. Relocation is now explicitly opt-in:

  • stabilizePromptCache?: boolean on anthropicToResponsesTranslation / translateAnthropicRequest (default false)
  • ordinary caller with that exact unfenced suffix keeps instructions and input unchanged
  • Claude Code /v1/messages inbound passes true; not inferred from metadata.user_id
  • Desktop prompt_cache_key: raw systemParts outside opt-in; opted-in hashes the same string as body.instructions

Verification: bun test inbound-cache-stabilize + inbound — 75 pass; bun run typecheck running. lidge-jun#3719 left open.

@cursor

cursor Bot commented Sep 11, 2026

Copy link
Copy Markdown

@Ingwannu addressed on 0847e21 (same head as lidge-jun#4052).

Kept the real N tokens left matcher, TaskCreate paragraph, trailing-unfenced-only peel, and unclosed-fence-to-EOF. Relocation is now explicitly opt-in:

  • stabilizePromptCache?: boolean on anthropicToResponsesTranslation / translateAnthropicRequest (default false)
  • ordinary caller with that exact unfenced suffix keeps instructions and input unchanged
  • Claude Code /v1/messages inbound passes true; not inferred from metadata.user_id
  • Desktop prompt_cache_key: raw systemParts outside opt-in; opted-in hashes the same string as body.instructions

Verification: inbound-cache-stabilize + inbound — 75 pass; bun run typecheck pass. lidge-jun#3719 left open.

@Warexpor

Copy link
Copy Markdown
Owner Author

Duplicate of upstream lidge-jun#4052 — same head branch . Closing to avoid split review.

@Warexpor Warexpor closed this Sep 11, 2026
lidge-jun added a commit that referenced this pull request Sep 15, 2026
…d hardened (lidge-jun#4285)

* feat(devin): add experimental Cognition (Devin/Windsurf) adapter

Carries the adapter from lidge-jun#4078 onto current dev and places the test in its
layout domain (tests/providers/devin-adapter.test.ts) with the layout map and
membership fixture updated.

Co-authored-by: Sayo <hi@sayo.wtf>

* fix(devin): harden the cloud-direct adapter before it ships

Four independent reviews of the carried lidge-jun#4078 adapter found one credential-leak
blocker, one abort blocker, and a set of routing and lifecycle defects.

Credentials: RegisterUser and GetUserJwt copied raw upstream bodies into
Error.message, which reaches CLI output, the adapter error event, and /api/logs.
A Connect error can quote the request, and the request holds the sign-in token or
the api_key; redactSecretString does not match a bare JWT. Every auth and chat
error now reports status, an allowlisted Connect code, and a trace id only. The
four credential-bearing POSTs stop following redirects, and the api-server host
is checked against a Cognition allowlist before it reaches a URL - including on
the way into auth.json, so an EU or FedStart tenant host survives a reload
instead of being dropped by the Copilot-only validator.

Routing: the adapter posted to the static registry baseUrl, so an EU or FedStart
account signed in and then sent every RPC to a server it is not provisioned on.
The signed-in account's tenant now decides the host.

Cancellation: after headers arrived nothing observed the caller's signal, so a
client cancel drained until the idle timer fired and then surfaced as
truncated_stream, while the adapter emitted neither done nor error and left the
bridge to synthesize adapter_eof. The body is cancelled on abort and the turn
reports the cancellation.

Also: a natural completion no longer reports stopReason "stop", which was
costing every clean turn its final_answer phase; sampling options reach the
cloud instead of its 128k/0.7 defaults; thinking stays out of replayed assistant
content; usage survives an error; gzip frames are bounded on output as well as
input; dotted model ids normalize to the catalog spelling; the session cache is
bounded; and logout clears the cached user_jwt whose payload carries the api_key.

Co-authored-by: Sayo <hi@sayo.wtf>

* test(devin): assemble the userinfo URL so the privacy scanner does not read it as an email

* feat(devin): pin the client version to the shipped release and accept the real token shape

Evidence from a live free-tier account and the shipped Devin Desktop 3.9.19
bundle. Details in devlog/_plan/260911_devin_two_providers/003_live_evidence.md.

The sign-in token is not a JWT. A real sign-in returns a 47-character
ott$<base64url> one-time value and RegisterUser accepts it, so the JWT-shape
gate would have rejected every real login. The paste parser now recognises one
opaque credential-shaped word rather than a token format.

RegisterUser returned api_server_url https://server.self-serve.windsurf.com for
an ordinary free account, which is what the tenant-routing fix in the previous
commit exists for: the hardcoded server.codeium.com was wrong for this account
before anyone reached an enterprise tenant.

The api-server allowlist gains the staging and beta hosts the shipped bundle
names, and the client version default moves from 2.0.0 - which predates the
Devin rebrand - to the 3.9.19 the desktop client reports, overridable through
OPENCODEX_DEVIN_CLIENT_VERSION.

Co-authored-by: Sayo <hi@sayo.wtf>

* docs(devin): carry the upstream MIT notice on the derived cloud-direct files

A similarity check against rsvedant/opencode-windsurf-auth puts wire.ts at 1.000,
index.ts at 0.988, chat.ts at 0.912, metadata.ts at 0.863, auth.ts at 0.835 and
catalog.ts at 0.753 - same module split, same comments, same field layout. These
files are a derivative of that repository's src/cloud-direct/, which is MIT
licensed, Copyright (c) 2026 Vedant, and the carry arrived with no notice at all.

The full permission notice sits in the module entry point and the other five
files carry a short attribution header pointing at it, which is what MIT asks
for in a distributed source tree.

Co-authored-by: Sayo <hi@sayo.wtf>

* docs(devin): record that the cloud chat path is unverified on a free account

* fix(devin): explain the opaque cloud denial instead of guessing at it

* docs(devin): restore the cloud provider rows and adapter sections on the new dev

devin-cli landed on dev as lidge-jun#4288, so this branch now carries only the cloud provider. The provider rows, the login line, and the adapters reference sections for devin are re-added on top of the current docs, and every locale still leads with the measured result that its chat path is unverified.

* fix(devin): restore the invalid_argument matcher the rebuild dropped

The rebuild re-applied the last commit's documentation by hand and its chat.ts
hunk went with it, so the runtime explanation was back to keying only on
permission_denied while the measured free account returns invalid_argument - the
one trailer it needed to fire for.

Also from the rebuild audit: the conformance test no longer names devin-cli in
guards the RUN_TURN_ONLY_WIRES set already skips, the two layout maps list the
devin test files alphabetically, and structure/adapters/registry.md records why
the cloud devin wire is a direct registry entry alongside devin-cli.

* feat(devin): fix the request encoding that made every chat turn fail

The cloud provider could not complete a single turn on any account. A paid
account settled what it was not: all 229 catalogue models came back enabled and
GetChatMessage failed exactly as it had on the free tier, so entitlement was
never the cause.

Importing the working reference's zero-dependency builder and sending its request
through our own transport returned HTTP 200 and a real stream, which put the
fault in our encoder rather than the wire. Diffing the two encoded messages field
by field left one difference: in CompletionConfiguration, #2 is the output cap
and #3 is the context window, and we had them swapped. A caller asking for 32
output tokens wrote 32 into the context-window field, and Cognition answered with
an opaque invalid_argument. Fields lidge-jun#6 and lidge-jun#11 are not part of the message at all.

A temperature of exactly 0 is refused with that same opaque error. Deterministic
output is the ordinary case for a coding client, so it is clamped to the smallest
accepted value rather than silently replaced with the service default.

Three transport facts had to hold together, which is why testing them one at a
time looked fruitless: the credential is the session token doubled and dash-joined
in an Authorization: Basic header while the body keeps one copy, the request
envelope is uncompressed, and Metadata lidge-jun#31 carries a 732-character fingerprint
whose length the service checks. The metadata identity is its own seven-field
shape rather than the desktop client's telemetry set, the request carries the
verified tag set, and the short-lived user_jwt is now opt-in because the chat path
does not need it.

Verified live on a paid account: six combinations, two hosts by three models, all
returning PONG with a finish reason and usage. A regression test pins the tag map
so the swap cannot return silently.

Co-authored-by: Sayo <hi@sayo.wtf>

* fix(devin): stop the opaque-denial message asserting a retracted explanation

The error string still told the user entitlement was proved and that the request
fields had been ruled out. That was the hypothesis this work retracted: the same
sentence came back for every turn until the CompletionConfiguration tag map was
corrected, and a temperature of exactly 0 still produces it. It now points at the
request first and names the test that pins the accepted field layout, and only
then at the account's model access.

Also from the pre-merge review: the comments claiming the hosted chat path needs
the user_jwt, the stale 128k output-default comment, the promptId that is now
optional because lidge-jun#22 is omitted on a first turn, and an English docs line that
claimed tool calls were verified when the live evidence is chat and usage across
three models.

---------

Co-authored-by: Sayo <hi@sayo.wtf>
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