From 87104c221b0705c190c1cc414ddb3179947ad319 Mon Sep 17 00:00:00 2001 From: David Wang <72378768+david-wang-0@users.noreply.github.com> Date: Mon, 7 Sep 2026 20:12:21 +0100 Subject: [PATCH 1/3] fix(claude): preserve OpenCode Go session affinity Co-authored-by: GPT-6 Astra Co-authored-by: Claude Fable 5.1 (cherry picked from commit a2909be453db7636da8ed4c13ad63b9ce5be6572) --- src/server/claude-messages.ts | 18 +++- .../opencode-go-session-header.test.ts | 93 ++++++++++++++++++- 2 files changed, 105 insertions(+), 6 deletions(-) diff --git a/src/server/claude-messages.ts b/src/server/claude-messages.ts index f6906de7e0..af81b63b65 100644 --- a/src/server/claude-messages.ts +++ b/src/server/claude-messages.ts @@ -30,12 +30,13 @@ import { import { clearableDeadline, idleDeadline } from "../lib/abort"; import { estimateTokens } from "../lib/token-estimate"; import { NoEligiblePolicyCandidateError, UnknownRoutingPolicyError, routeModel } from "../router"; +import { registryEntryForProviderDestination } from "../providers/registry"; import { evidenceFromBody } from "../routing/request-evidence"; import { resolveWireProtocolOverride } from "./adapter-resolve"; import type { OcxConfig } from "../types"; import { readJsonRequestBody } from "./request-decompress"; import { addFinalRequestLog, httpStatusForRequestLogTerminal, recordFirstOutput, type RequestLogContext, type RequestLogEntry } from "./request-log"; -import { conversationIdFromClaudeMetadata } from "./request-log-conversation"; +import { conversationIdFromClaudeMetadata, sessionLaneIdFromRequest } from "./request-log-conversation"; import { responseWithDeferredRequestLog } from "./relay"; import { handleResponses } from "./responses"; import { @@ -786,8 +787,12 @@ async function handleClaudeMessagesWithBudget( // bodies: it 400s on sampling params ("Unsupported parameter: max_output_tokens", // verified live 2026-07-11). Strip them for that route; routed providers keep them. let nativeRoute = false; + let opencodeGoRoute = false; try { const route = routeModel(config, internalBody.model as string, evidenceFromBody(internalBody)); + // Match the fixed key-auth destination before per-model wire overrides, including + // renamed Go providers without treating custom or lookalike URLs as Go. + opencodeGoRoute = registryEntryForProviderDestination(route.provider)?.id === "opencode-go"; // Settle the wire once so the sampling decision below reads the effective // adapter rather than the provider-wide default (#404). route.provider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, "anthropic"); @@ -851,11 +856,18 @@ async function handleClaudeMessagesWithBudget( headers.set("chatgpt-account-id", token.chatgptAccountId); } } - if (nativeRoute) { + if (opencodeGoRoute) { + const session = req.headers.get("x-opencode-session"); + if (session) headers.set("x-opencode-session", session); + } + const hasExplicitGoSession = opencodeGoRoute + && (sessionLaneIdFromRequest(headers) !== undefined || headers.has("x-opencode-session")); + if ((nativeRoute || opencodeGoRoute) && !hasExplicitGoSession) { // ChatGPT-backend prompt-cache affinity rides the session_id HEADER (codex // clients always send their session uuid; devlog 090 follow-up: body-level // prompt_cache_key alone still yielded cached_tokens:0). Claude Code never sends - // the header, so synthesize a stable per-session uuid from the same cache key — + // the header, so synthesize a stable per-session uuid from the same cache key. + // Routed Go requests need this lane too for their x-opencode-session affinity — // but ONLY for a real per-session key (metadata.user_id). The system-hash fallback // key is shared across Desktop conversations, and a shared session_id's backend // semantics are unproven (audit 133 R2#3): body prompt_cache_key only there. diff --git a/tests/providers/opencode-go-session-header.test.ts b/tests/providers/opencode-go-session-header.test.ts index ab28c8475f..de3667c8fd 100644 --- a/tests/providers/opencode-go-session-header.test.ts +++ b/tests/providers/opencode-go-session-header.test.ts @@ -4,6 +4,7 @@ import { resolveOpenCodeGoTransport } from "../../src/providers/opencode-go-tran import { getProviderRegistryEntry } from "../../src/providers/registry"; import { handleResponses } from "../../src/server/responses/core"; import { handleChatCompletions } from "../../src/server/chat-completions"; +import { handleClaudeMessages } from "../../src/server/claude-messages"; import type { OcxConfig, OcxProviderConfig } from "../../src/types"; const MUSE_MODEL = "muse-spark-1.3-contributor"; @@ -25,7 +26,14 @@ function codexHeaders(child = "child-thread-a"): Record { }; } -function upstreamResponse(url: string): Response { +function upstreamResponse(url: string, stream = false): Response { + if (stream && url.endsWith("/chat/completions")) { + return new Response([ + `data: ${JSON.stringify({ choices: [{ index: 0, delta: { role: "assistant", content: "ok" } }] })}\n\n`, + `data: ${JSON.stringify({ choices: [{ index: 0, delta: {}, finish_reason: "stop" }], usage: { prompt_tokens: 1, completion_tokens: 1 } })}\n\n`, + "data: [DONE]\n\n", + ].join(""), { headers: { "content-type": "text/event-stream" } }); + } if (url.endsWith("/responses")) { return Response.json({ id: "resp_opencode_go_session", @@ -54,6 +62,8 @@ async function captureRequest(input: { child?: string; provider?: OcxProviderConfig; nativeChat?: boolean; + claude?: boolean; + metadataUserId?: string; headers?: Record; } = {}): Promise<{ url: string; headers: Headers }> { const providerName = input.providerName ?? "opencode-go"; @@ -62,13 +72,26 @@ async function captureRequest(input: { globalThis.fetch = (async (requestInput: RequestInfo | URL, init?: RequestInit) => { const url = String(requestInput); requests.push({ url, headers: new Headers(init?.headers) }); - return upstreamResponse(url); + return upstreamResponse(url, input.claude); }) as typeof fetch; const config = { providers: { [providerName]: input.provider ?? opencodeGo() }, } as unknown as OcxConfig; - const response = input.nativeChat ? await handleChatCompletions( + const response = input.claude ? await handleClaudeMessages( + new Request("http://localhost/v1/messages", { + method: "POST", + headers: input.headers ?? { "content-type": "application/json" }, + body: JSON.stringify({ + model: `${providerName}/${model}`, max_tokens: 64, stream: false, + system: "A shared system prompt is not a conversation identifier.", + messages: [{ role: "user", content: "ping" }], + ...(input.metadataUserId ? { metadata: { user_id: input.metadataUserId } } : {}), + }), + }), + config, + { model: "", provider: "" }, + ) : input.nativeChat ? await handleChatCompletions( new Request("http://localhost/v1/chat/completions", { method: "POST", headers: input.headers ?? codexHeaders(input.child), @@ -97,6 +120,70 @@ describe("OpenCode Go session affinity (#3344)", () => { const originalFetch = globalThis.fetch; afterEach(() => { globalThis.fetch = originalFetch; }); + test("Claude metadata gives stable Go affinity across turns and distinct conversations", async () => { + const input = { claude: true, model: CHAT_MODEL, metadataUserId: "user_test_account__session_conversation-a" }; + const first = await captureRequest(input); + const continued = await captureRequest(input); + const next = await captureRequest({ ...input, metadataUserId: "user_test_account__session_conversation-b" }); + expect(first.url).toBe("https://opencode.ai/zen/go/v1/chat/completions"); + expect(first.headers.get(SESSION_HEADER)).toMatch(/^ocx_[0-9a-f]{32}$/); + expect(continued.headers.get(SESSION_HEADER)).toBe(first.headers.get(SESSION_HEADER)); + expect(next.headers.get(SESSION_HEADER)).not.toBe(first.headers.get(SESSION_HEADER)); + expect(first.headers.get(SESSION_HEADER)).not.toContain("conversation-a"); + }); + + test("Claude recognizes renamed canonical Go destinations and omits shared system affinity", async () => { + const input = { claude: true, model: CHAT_MODEL, providerName: "renamed-go" }; + const metadata = await captureRequest({ ...input, metadataUserId: "user_test_account__session_conversation-a" }); + const desktop = await captureRequest(input); + expect(metadata.headers.get(SESSION_HEADER)).toMatch(/^ocx_[0-9a-f]{32}$/); + expect(desktop.headers.has(SESSION_HEADER)).toBe(false); + }); + + test("Claude explicit Go header precedes metadata and matches native Chat affinity", async () => { + const headers = { "content-type": "application/json", [SESSION_HEADER]: "client-session-a" }; + const claude = await captureRequest({ claude: true, model: CHAT_MODEL, headers, metadataUserId: "different-metadata-session" }); + const chat = await captureRequest({ nativeChat: true, model: CHAT_MODEL, headers }); + expect(claude.headers.get(SESSION_HEADER)).toBe("ocx_516d593899f34b7baca2db37c7b0c8c5"); + expect(claude.headers.get(SESSION_HEADER)).toBe(chat.headers.get(SESSION_HEADER)); + }); + + test("Claude affinity survives per-model Responses wire selection", async () => { + const input = { claude: true, metadataUserId: "user_test_account__session_conversation-a" }; + const chat = await captureRequest({ ...input, model: CHAT_MODEL }); + const responses = await captureRequest({ ...input, model: MUSE_MODEL }); + expect(responses.url).toBe("https://opencode.ai/zen/go/v1/responses"); + expect(responses.headers.get(SESSION_HEADER)).toBe(chat.headers.get(SESSION_HEADER)); + const explicit = await captureRequest({ + ...input, model: MUSE_MODEL, + headers: { "content-type": "application/json", [SESSION_HEADER]: "client-session-a" }, + }); + expect(explicit.headers.get(SESSION_HEADER)).toBe("ocx_516d593899f34b7baca2db37c7b0c8c5"); + }); + + test("Claude preserves explicit session lanes and operator header precedence", async () => { + for (const laneHeader of ["session_id", "session-id", "thread-id"]) { + const headers = { "content-type": "application/json", [laneHeader]: "native-client-session", [SESSION_HEADER]: "different-fallback" }; + const input = { claude: true, model: CHAT_MODEL, headers, metadataUserId: "different-metadata-session" }; + const claude = await captureRequest(input); + const native = await captureRequest({ model: CHAT_MODEL, headers }); + expect(claude.headers.get(SESSION_HEADER)).toBe(native.headers.get(SESSION_HEADER)); + const operator = await captureRequest({ ...input, provider: opencodeGo({ headers: { "X-OpenCode-Session": "operator-session" } }) }); + expect(operator.headers.get(SESSION_HEADER)).toBe("operator-session"); + } + }); + + test("Claude does not add Go affinity to custom or lookalike destinations", async () => { + for (const baseUrl of ["https://custom.example/v1", "https://opencode.ai.evil.test/zen/go/v1"]) { + const captured = await captureRequest({ + claude: true, model: CHAT_MODEL, providerName: "custom-go", + provider: opencodeGo({ baseUrl }), metadataUserId: "user_test_account__session_conversation-a", + headers: { "content-type": "application/json", [SESSION_HEADER]: "client-session-a" }, + }); + expect(captured.headers.has(SESSION_HEADER)).toBe(false); + } + }); + test("native Chat ingress preserves stable Go affinity and separates conversations", async () => { const provider = opencodeGo(); const input = { nativeChat: true, model: "omen-alpha", provider }; From 6036f55c4d76dc2a42fab65020cccdb7bd94d176 Mon Sep 17 00:00:00 2001 From: JUN <243035832+lidge-jun@users.noreply.github.com> Date: Tue, 8 Sep 2026 07:54:11 +0900 Subject: [PATCH 2/3] fix(claude): gate Go affinity on accepted session identity Count explicit OpenCode session identity only when the downstream normalizer accepts it, allowing valid metadata fallback. Require usable metadata for Go synthesis on both Chat and Responses wires while preserving native ChatGPT synthesis. Add final-outbound contract cases for invalid and oversized headers, absent and unusable metadata, and explicit lane/operator precedence using independent fixed vectors. Local tests, typecheck, build, install, and runtime checks: NOT RUN by user instruction; patch self-inspected with Git/source/diff only. Source-commit: a2909be453db7636da8ed4c13ad63b9ce5be6572 Co-authored-by: David Wang <72378768+david-wang-0@users.noreply.github.com> Co-authored-by: GPT-6 Astra Co-authored-by: Claude Fable 5.1 --- src/server/claude-messages.ts | 11 ++- .../opencode-go-session-header.test.ts | 74 +++++++++++++++---- 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/src/server/claude-messages.ts b/src/server/claude-messages.ts index af81b63b65..41ea301344 100644 --- a/src/server/claude-messages.ts +++ b/src/server/claude-messages.ts @@ -36,7 +36,7 @@ import { resolveWireProtocolOverride } from "./adapter-resolve"; import type { OcxConfig } from "../types"; import { readJsonRequestBody } from "./request-decompress"; import { addFinalRequestLog, httpStatusForRequestLogTerminal, recordFirstOutput, type RequestLogContext, type RequestLogEntry } from "./request-log"; -import { conversationIdFromClaudeMetadata, sessionLaneIdFromRequest } from "./request-log-conversation"; +import { conversationIdFromClaudeMetadata, normalizeLogConversationId, sessionLaneIdFromRequest } from "./request-log-conversation"; import { responseWithDeferredRequestLog } from "./relay"; import { handleResponses } from "./responses"; import { @@ -861,8 +861,13 @@ async function handleClaudeMessagesWithBudget( if (session) headers.set("x-opencode-session", session); } const hasExplicitGoSession = opencodeGoRoute - && (sessionLaneIdFromRequest(headers) !== undefined || headers.has("x-opencode-session")); - if ((nativeRoute || opencodeGoRoute) && !hasExplicitGoSession) { + && (sessionLaneIdFromRequest(headers) !== undefined + || normalizeLogConversationId(headers.get("x-opencode-session")) !== undefined); + const synthesizeGoSession = opencodeGoRoute && !hasExplicitGoSession + && isRec(anthropicBody) + && conversationIdFromClaudeMetadata(isRec(anthropicBody.metadata) ? anthropicBody.metadata : undefined) !== undefined; + // Go can also use the Responses adapter; its eligibility gate must win on both wires. + if (opencodeGoRoute ? synthesizeGoSession : nativeRoute) { // ChatGPT-backend prompt-cache affinity rides the session_id HEADER (codex // clients always send their session uuid; devlog 090 follow-up: body-level // prompt_cache_key alone still yielded cached_tokens:0). Claude Code never sends diff --git a/tests/providers/opencode-go-session-header.test.ts b/tests/providers/opencode-go-session-header.test.ts index de3667c8fd..f66ce8ab9c 100644 --- a/tests/providers/opencode-go-session-header.test.ts +++ b/tests/providers/opencode-go-session-header.test.ts @@ -86,7 +86,7 @@ async function captureRequest(input: { model: `${providerName}/${model}`, max_tokens: 64, stream: false, system: "A shared system prompt is not a conversation identifier.", messages: [{ role: "user", content: "ping" }], - ...(input.metadataUserId ? { metadata: { user_id: input.metadataUserId } } : {}), + ...(input.metadataUserId !== undefined ? { metadata: { user_id: input.metadataUserId } } : {}), }), }), config, @@ -126,9 +126,10 @@ describe("OpenCode Go session affinity (#3344)", () => { const continued = await captureRequest(input); const next = await captureRequest({ ...input, metadataUserId: "user_test_account__session_conversation-b" }); expect(first.url).toBe("https://opencode.ai/zen/go/v1/chat/completions"); - expect(first.headers.get(SESSION_HEADER)).toMatch(/^ocx_[0-9a-f]{32}$/); - expect(continued.headers.get(SESSION_HEADER)).toBe(first.headers.get(SESSION_HEADER)); - expect(next.headers.get(SESSION_HEADER)).not.toBe(first.headers.get(SESSION_HEADER)); + // Fixed SHA-256 vectors calculated independently of the production helpers. + expect(first.headers.get(SESSION_HEADER)).toBe("ocx_a89540229ef781fd5f7adf92a711b436"); + expect(continued.headers.get(SESSION_HEADER)).toBe("ocx_a89540229ef781fd5f7adf92a711b436"); + expect(next.headers.get(SESSION_HEADER)).toBe("ocx_55fec02e7f2c7f9358958ab6d1589530"); expect(first.headers.get(SESSION_HEADER)).not.toContain("conversation-a"); }); @@ -136,7 +137,7 @@ describe("OpenCode Go session affinity (#3344)", () => { const input = { claude: true, model: CHAT_MODEL, providerName: "renamed-go" }; const metadata = await captureRequest({ ...input, metadataUserId: "user_test_account__session_conversation-a" }); const desktop = await captureRequest(input); - expect(metadata.headers.get(SESSION_HEADER)).toMatch(/^ocx_[0-9a-f]{32}$/); + expect(metadata.headers.get(SESSION_HEADER)).toBe("ocx_a89540229ef781fd5f7adf92a711b436"); expect(desktop.headers.has(SESSION_HEADER)).toBe(false); }); @@ -145,7 +146,7 @@ describe("OpenCode Go session affinity (#3344)", () => { const claude = await captureRequest({ claude: true, model: CHAT_MODEL, headers, metadataUserId: "different-metadata-session" }); const chat = await captureRequest({ nativeChat: true, model: CHAT_MODEL, headers }); expect(claude.headers.get(SESSION_HEADER)).toBe("ocx_516d593899f34b7baca2db37c7b0c8c5"); - expect(claude.headers.get(SESSION_HEADER)).toBe(chat.headers.get(SESSION_HEADER)); + expect(chat.headers.get(SESSION_HEADER)).toBe("ocx_516d593899f34b7baca2db37c7b0c8c5"); }); test("Claude affinity survives per-model Responses wire selection", async () => { @@ -153,7 +154,8 @@ describe("OpenCode Go session affinity (#3344)", () => { const chat = await captureRequest({ ...input, model: CHAT_MODEL }); const responses = await captureRequest({ ...input, model: MUSE_MODEL }); expect(responses.url).toBe("https://opencode.ai/zen/go/v1/responses"); - expect(responses.headers.get(SESSION_HEADER)).toBe(chat.headers.get(SESSION_HEADER)); + expect(chat.headers.get(SESSION_HEADER)).toBe("ocx_a89540229ef781fd5f7adf92a711b436"); + expect(responses.headers.get(SESSION_HEADER)).toBe("ocx_a89540229ef781fd5f7adf92a711b436"); const explicit = await captureRequest({ ...input, model: MUSE_MODEL, headers: { "content-type": "application/json", [SESSION_HEADER]: "client-session-a" }, @@ -161,17 +163,57 @@ describe("OpenCode Go session affinity (#3344)", () => { expect(explicit.headers.get(SESSION_HEADER)).toBe("ocx_516d593899f34b7baca2db37c7b0c8c5"); }); - test("Claude preserves explicit session lanes and operator header precedence", async () => { - for (const laneHeader of ["session_id", "session-id", "thread-id"]) { - const headers = { "content-type": "application/json", [laneHeader]: "native-client-session", [SESSION_HEADER]: "different-fallback" }; - const input = { claude: true, model: CHAT_MODEL, headers, metadataUserId: "different-metadata-session" }; - const claude = await captureRequest(input); - const native = await captureRequest({ model: CHAT_MODEL, headers }); - expect(claude.headers.get(SESSION_HEADER)).toBe(native.headers.get(SESSION_HEADER)); + for (const [model, url] of [ + [CHAT_MODEL, "https://opencode.ai/zen/go/v1/chat/completions"], + [MUSE_MODEL, "https://opencode.ai/zen/go/v1/responses"], + ] as const) { + test(`Claude ${model} falls back to valid metadata after invalid explicit Go identity`, async () => { + // Interior tab is constructible in HTTP Headers but rejected by the identity owner. + for (const session of ["", " ", "invalid\tidentity", "x".repeat(4097)]) { + const captured = await captureRequest({ + claude: true, model, metadataUserId: "user_test_account__session_conversation-a", + headers: { "content-type": "application/json", [SESSION_HEADER]: session }, + }); + expect(captured.url).toBe(url); + expect(captured.headers.get(SESSION_HEADER)).toBe("ocx_a89540229ef781fd5f7adf92a711b436"); + } + }); + + test(`Claude ${model} omits Go affinity without usable metadata identity`, async () => { + for (const metadataUserId of [undefined, "", " \t\n ", "invalid\u0000identity", "x".repeat(4097)]) { + const captured = await captureRequest({ claude: true, model, metadataUserId }); + expect(captured.url).toBe(url); + expect(captured.headers.has(SESSION_HEADER)).toBe(false); + expect(captured.headers.has("session_id")).toBe(false); + } + }); + + test(`Claude ${model} keeps explicit and operator identity with empty metadata`, async () => { + const input = { + claude: true, model, metadataUserId: "", + headers: { "content-type": "application/json", [SESSION_HEADER]: " client-session-a " }, + }; + const explicit = await captureRequest(input); + expect(explicit.url).toBe(url); + expect(explicit.headers.get(SESSION_HEADER)).toBe("ocx_516d593899f34b7baca2db37c7b0c8c5"); const operator = await captureRequest({ ...input, provider: opencodeGo({ headers: { "X-OpenCode-Session": "operator-session" } }) }); + expect(operator.url).toBe(url); expect(operator.headers.get(SESSION_HEADER)).toBe("operator-session"); - } - }); + }); + + test(`Claude ${model} preserves explicit session lanes and operator header precedence`, async () => { + for (const laneHeader of ["session_id", "session-id", "thread-id", "x-codex-parent-thread-id"]) { + const headers = { "content-type": "application/json", [laneHeader]: "native-client-session", [SESSION_HEADER]: "different-fallback" }; + const input = { claude: true, model, headers, metadataUserId: "different-metadata-session" }; + const claude = await captureRequest(input); + expect(claude.url).toBe(url); + expect(claude.headers.get(SESSION_HEADER)).toBe("ocx_a197dbb87311c29a5fbe51140e3845ce"); + const operator = await captureRequest({ ...input, provider: opencodeGo({ headers: { "X-OpenCode-Session": "operator-session" } }) }); + expect(operator.url).toBe(url); + expect(operator.headers.get(SESSION_HEADER)).toBe("operator-session"); + } + }); + } test("Claude does not add Go affinity to custom or lookalike destinations", async () => { for (const baseUrl of ["https://custom.example/v1", "https://opencode.ai.evil.test/zen/go/v1"]) { From ac8010db910ed598770e05f454e552a3865f54e4 Mon Sep 17 00:00:00 2001 From: JUN <243035832+lidge-jun@users.noreply.github.com> Date: Tue, 8 Sep 2026 07:58:05 +0900 Subject: [PATCH 3/3] fix(claude): replace unusable Go lane headers during metadata fallback --- src/server/claude-messages.ts | 2 +- tests/providers/opencode-go-session-header.test.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/server/claude-messages.ts b/src/server/claude-messages.ts index 41ea301344..8c3e37eea8 100644 --- a/src/server/claude-messages.ts +++ b/src/server/claude-messages.ts @@ -876,7 +876,7 @@ async function handleClaudeMessagesWithBudget( // but ONLY for a real per-session key (metadata.user_id). The system-hash fallback // key is shared across Desktop conversations, and a shared session_id's backend // semantics are unproven (audit 133 R2#3): body prompt_cache_key only there. - if (cacheKeySource === "metadata" && !headers.has("session_id") && typeof internalBody.prompt_cache_key === "string") { + if (cacheKeySource === "metadata" && (synthesizeGoSession || !headers.has("session_id")) && typeof internalBody.prompt_cache_key === "string") { headers.set("session_id", uuidFromHex(internalBody.prompt_cache_key)); } } diff --git a/tests/providers/opencode-go-session-header.test.ts b/tests/providers/opencode-go-session-header.test.ts index f66ce8ab9c..c176dc703e 100644 --- a/tests/providers/opencode-go-session-header.test.ts +++ b/tests/providers/opencode-go-session-header.test.ts @@ -176,6 +176,12 @@ describe("OpenCode Go session affinity (#3344)", () => { }); expect(captured.url).toBe(url); expect(captured.headers.get(SESSION_HEADER)).toBe("ocx_a89540229ef781fd5f7adf92a711b436"); + const invalidLane = await captureRequest({ + claude: true, model, metadataUserId: "user_test_account__session_conversation-a", + headers: { "content-type": "application/json", session_id: session }, + }); + expect(invalidLane.url).toBe(url); + expect(invalidLane.headers.get(SESSION_HEADER)).toBe("ocx_a89540229ef781fd5f7adf92a711b436"); } });