Repository navigation
[Bug]: antigravity wire session id is derived from first-user text, so it changes when the client compacts history #1297
Description
Client or integration
Codex CLI
Area
Provider adapter
Summary
antigravitySessionId() (src/adapters/google-antigravity-wire.ts) derives the wire session id from a SHA-256 of the first user message text, even when the request already carries a real conversation identity.
The function's own comment states the requirement it has to satisfy: the id must stay stable across every turn, because the replay cache observes thought signatures on turn N's response and re-injects them on turn N+1's request, so both turns must map to the same id.
First-user text does not reliably satisfy that. It is stable only while the first user message survives verbatim in the payload the adapter sees. Once history is compacted, summarised, or trimmed — which long Codex sessions do — the anchor text changes, the derived id changes mid-conversation, and the replay cache stops matching. The symptom is thought signatures silently not being re-injected on continuation turns, which on the Gemini side degrades or 400s exactly where signature replay matters.
Meanwhile the request context often already carries something purpose-built for this: a conversation id, a prompt cache key, or the client thread id.
Expected: prefer a real conversation identity when one is present, and keep the text hash only as a deterministic fallback for clients that send no conversation metadata.
Secondary, lower-severity consequence of text anchoring: two distinct conversations that open with identical text (a template prompt, a slash command, a bare "hi") map to the same session id. The current comment argues this is harmless because the replay cache keys signatures on functionCall identity (name + args) rather than on the id alone. That reasoning holds for collisions, but it does not help the instability case above — a changing id, unlike a shared one, loses the association entirely.
Reproduction
Evidence level, stated plainly: this is a mechanism-level report, not a captured failure. I have not recorded a Codex session where a compaction visibly broke signature replay; I found it while reviewing the adapter and cannot currently produce a deterministic capture. Reporting it because the invariant the code documents for itself is not guaranteed by the value it chose.
The reasoning is checkable without a running provider:
- Read
antigravitySessionId()insrc/adapters/google-antigravity-wire.ts. It callsfirstUserText(parsed)and hashes the result; with no text it returns a random id. - Note the comment above it stating that the id must be identical on turn N and turn N+1 for the replay cache to re-inject signatures.
- Construct two
OcxParsedRequestvalues representing consecutive turns of one conversation where the earliest user message has been dropped or summarised by the client, as history compaction does.firstUserTextreturns different strings, so the derived ids differ, so a signature cached under turn N's id is not found under turn N+1's. - The same holds trivially for the textless case, where the fallback is
-${Math.floor(Math.random() * 9e18)}— a fresh id on every turn by construction.
Observable path on a live install, for whoever can hold a long enough session:
- Configure a
google-antigravityprovider (adapter: google,googleMode: cloud-code-assist). - Run a Codex session with tool calls until the client compacts history.
- Compare the wire session id sent before and after compaction; then check whether
thoughtSignaturevalues observed pre-compaction are still replayed after it.
Suggested fix — prefer, in order, any non-empty conversation identity already on the request, then fall back to the current behaviour:
const stableContextId = [
parsed.context.conversationId,
parsed.context.promptCacheKey,
parsed.options.promptCacheKey,
parsed._clientThreadId,
].find((v): v is string => typeof v === "string" && v.trim().length > 0);
if (stableContextId) return stableSessionId(stableContextId);
// unchanged fallback for clients that send no conversation metadata
const text = firstUserText(parsed);
if (!text) return `-${Math.floor(Math.random() * 9e18).toString()}`;
return stableSessionId(text);conversationId / promptCacheKey are not on OcxContext today, so this also needs them threaded from the request context — in my local build that meant populating them in src/server/responses/core.ts from x-codex-parent-thread-id and the existing prompt cache key.
I have been running this shape locally since 2.10.x across antigravity routes without regression, but I want to be clear that "no regression observed" is weaker evidence than a captured failure, and I am not claiming the latter.
Version
2.11.0
Operating system
Windows 11 Home Single Language 26H2 (build 10.0.26200)
Provider and model
google-antigravity / gemini-3.6-flash, claude-opus-4-6-thinking
Logs or error output
No captured failure log — see the evidence note in Reproduction.
The report is based on reading src/adapters/google-antigravity-wire.ts
against the stability invariant its own comment states.Screenshots and supporting files
Not attached.
Redacted configuration
{
"providers": {
"google-antigravity": {
"adapter": "google",
"baseUrl": "https://daily-cloudcode-pa.googleapis.com",
"authMode": "oauth",
"googleMode": "cloud-code-assist",
"defaultModel": "gemini-3.6-flash"
}
}
}Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
github-actions commentedon Aug 8, 2026
Automated translation bookkeeping — detected language: unknown.
lidge-jun commentedon Aug 8, 2026
@brunoflma Confirmed by reading, exactly as you framed it, and fixed in #1311.
Your separation of the two failure modes is the part that made this actionable. The existing comment defends collisions by noting the replay cache keys on
functionCallidentity, and that argument is sound — it just says nothing about instability. A shared id still finds the association; a changed id loses it. I kept that distinction in the code comment rather than replacing it, because the original reasoning was correct about the thing it was reasoning about.The fix anchors on
_clientThreadId(x-codex-parent-thread-id), which does not depend on message content, so compaction cannot move it.Three things I want on the record, since you were careful about evidence level and this deserves the same.
First, I nearly used
promptCacheKey— you list it among the candidates, and it looked right. It is not: it is arbitrary Responses input and is explicitly shared across conversations for some clients, so it identifies a cache cohort rather than a conversation.adapters/cursor/request-builder.tsalready warns against exactly this.Second, and this is a related defect you did not report:
vertexReplaySessionId(adapters/google.ts:56) does usepromptCacheKey, under a comment saying it prefers a stable thread key. I initially read that as the correct pattern to copy. It is a second instance of your bug on the Vertex replay namespace, differently shaped. Not fixed in #1311 — filing it separately would be better than bundling it into a fix for a different path, so consider this the note that it exists.Third, the honest limits: a client sending no thread header keeps the text anchor and its instability, and I have no live CCA evidence that Google benefits from the changed identity — only that the local association now survives compaction. Deploying it also changes the Google-visible
sessionIdfor conversations already in flight.The tests include an envelope-level one asserting
request.sessionIdin the built CCA body stays stable across turns with differing first-user text, since proving the derivation is not the same as proving it reaches the wire.fix(google): anchor the CCA session id on the client thread id (#1297) (
lidge-jun commentedon Aug 8, 2026
Fixed on
devas b5d44a5 (#1311). The CCA session id now anchors on_clientThreadId, so compaction no longer moves it mid-conversation.Closing manually — PRs here target
dev, so GitHub does not auto-close.Leaving the Vertex counterpart out of this closure deliberately:
vertexReplaySessionIdstill usespromptCacheKeyunder a comment claiming a stable thread key, which is the same defect on the Vertex replay namespace. It is untouched by #1311 and worth its own issue rather than being quietly absorbed into this one.docs(devlog): record WP6 and WP17 of the bug campaign (#1314)
fix(google): anchor the CCA session id on the client thread id (lidge…
docs(devlog): record WP6 and WP17 of the bug campaign (lidge-jun#1314)