Skip to content

fix(cloaking): string-format system prompt silently dropped, causing lost user instructions#1990

Closed
shellus wants to merge 2 commits intorouter-for-me:mainfrom
shellus:fix/cloaking-string-system-prompt
Closed

fix(cloaking): string-format system prompt silently dropped, causing lost user instructions#1990
shellus wants to merge 2 commits intorouter-for-me:mainfrom
shellus:fix/cloaking-string-system-prompt

Conversation

@shellus
Copy link

@shellus shellus commented Mar 9, 2026

Problem

checkSystemInstructionsWithMode only handles array-format system prompts ("system": [...]). The Anthropic API also accepts string-format system prompts ("system": "text"), which many third-party clients use (OpenAI SDK adapters, custom chat applications, etc.).

When a string-format system prompt is received, the existing code skips the system.IsArray() branch and the user's prompt is silently dropped. The resulting cloaked payload contains only 2 system blocks (billing header + agent identity), missing the user's original content.

This causes:

  1. User system prompt lost — the AI never receives the configured assistant persona/instructions.
  2. Upstream validation failures — some upstreams require 3+ system blocks in cloaked requests, rejecting payloads with only 2.

Fix

Add an else if system.Type == gjson.String branch in checkSystemInstructionsWithMode to convert the string prompt into a proper array element (with cache_control) appended after the billing and agent blocks. Empty strings are skipped to avoid injecting a blank block.

Tests

Added 5 test cases covering:

  • String system prompt → converted to 3rd array element ✅
  • String with special characters (newlines, tabs, quotes) ✅
  • Empty string → no extra block injected ✅
  • Array system prompt → existing behavior preserved ✅
  • No system field → existing behavior preserved ✅

…tructionsWithMode

checkSystemInstructionsWithMode only handled array-format system prompts.
When a client sends a string-format system prompt (e.g. "system": "You
are a helpful assistant"), the user's prompt was silently dropped because
the code only iterated over system.IsArray() elements.

This caused two issues:
1. The user's system prompt was lost — the AI never received it.
2. The cloaked payload contained only 2 system blocks (billing + agent
   identity) instead of the expected 3+, which some upstreams validate.

Add an else-if branch for gjson.String to convert the string into an
array element with cache_control, appended after the billing and agent
blocks. Empty strings are skipped to avoid injecting a blank block.
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical bug in the system prompt handling mechanism where user-provided system instructions, when supplied as a simple string, were being inadvertently discarded. This oversight resulted in AI models not receiving their intended persona or instructions and could cause downstream API validation errors. The changes introduce a robust parsing and conversion process for string-based system prompts, ensuring they are correctly integrated into the request payload and preserving the user's intended model behavior, thereby enhancing the reliability and correctness of API interactions.

Highlights

  • Bug Fix: String-Format System Prompts: Resolved an issue where string-format system prompts were silently dropped by checkSystemInstructionsWithMode, leading to lost user instructions and potential upstream validation failures.
  • Enhanced System Prompt Handling: Implemented new logic in checkSystemInstructionsWithMode to correctly parse and convert string-format system prompts into an array element, ensuring they are properly included in the cloaked payload with appropriate cache_control.
  • Robustness for Special Characters: Added escaping for special characters (newlines, tabs, quotes) within string-format system prompts to maintain data integrity during conversion.
  • Comprehensive Test Coverage: Introduced five new test cases to validate the fix, covering scenarios such as string prompts, prompts with special characters, empty string prompts, and confirming existing behavior for array-format and missing system prompts.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • internal/runtime/executor/claude_executor.go
    • Added an else if condition to detect and process string-format system prompts.
    • Implemented logic to escape special characters (backslash, double quote, newline, carriage return, tab) within the string prompt.
    • Converted the escaped string prompt into a JSON array element with type: "text", text content, and cache_control: {type: "ephemeral"}.
    • Appended the newly formed system prompt array element to the existing system instruction blocks.
  • internal/runtime/executor/claude_executor_test.go
    • Added TestCheckSystemInstructions_StringSystemPrompt to verify conversion of a basic string prompt to an array element.
    • Added TestCheckSystemInstructions_StringSystemPromptWithSpecialChars to ensure special characters within string prompts are correctly preserved.
    • Added TestCheckSystemInstructions_EmptyStringSystemPrompt to confirm that empty string prompts do not add an extra block.
    • Added TestCheckSystemInstructions_ArraySystemPrompt to ensure existing array-format system prompt handling remains unchanged.
    • Added TestCheckSystemInstructions_NoSystemField to verify correct behavior when no system field is present.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request correctly addresses an issue where string-format system prompts were being dropped. The fix involves converting string prompts to the expected array format, and the addition of comprehensive test cases is a great way to ensure correctness and prevent regressions. I have a couple of suggestions to improve the implementation. One is to use the standard library for JSON escaping to make the code more robust and secure. The other is to strengthen an assertion in one of the new tests.

Comment on lines +1272 to +1277
escaped := strings.ReplaceAll(system.String(), `\`, `\\`)
escaped = strings.ReplaceAll(escaped, `"`, `\"`)
escaped = strings.ReplaceAll(escaped, "\n", `\n`)
escaped = strings.ReplaceAll(escaped, "\r", `\r`)
escaped = strings.ReplaceAll(escaped, "\t", `\t`)
result += `,{"type":"text","text":"` + escaped + `","cache_control":{"type":"ephemeral"}}`
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Manually escaping strings for JSON is error-prone and can miss edge cases, potentially leading to malformed JSON or security vulnerabilities. The current implementation handles common characters like \, ", \n, \r, and \t, but it doesn't cover all control characters that must be escaped according to the JSON specification (e.g., \b, \f).

Using json.Marshal from the standard library is a safer and more robust approach as it handles all necessary escaping automatically.

Suggested change
escaped := strings.ReplaceAll(system.String(), `\`, `\\`)
escaped = strings.ReplaceAll(escaped, `"`, `\"`)
escaped = strings.ReplaceAll(escaped, "\n", `\n`)
escaped = strings.ReplaceAll(escaped, "\r", `\r`)
escaped = strings.ReplaceAll(escaped, "\t", `\t`)
result += `,{"type":"text","text":"` + escaped + `","cache_control":{"type":"ephemeral"}}`
escapedBytes, _ := json.Marshal(system.String())
result += `,{"type":"text","text":` + string(escapedBytes) + `,"cache_control":{"type":"ephemeral"}}`

Copy link
Author

Choose a reason for hiding this comment

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

Good catch. Replaced manual escaping with json.Marshal — it handles all control characters per the JSON spec. Fixed in d2474c0.

Comment on lines +1007 to +1009
if !strings.Contains(got, "line1") || !strings.Contains(got, "line2") {
t.Fatalf("system[2] should preserve multiline content, got: %s", got)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The current assertion only checks for the presence of substrings (line1, line2), which is a bit weak. For a more robust test, it's better to assert that the entire string matches the expected value exactly. This will catch any unintended changes to whitespace, special characters, or their ordering.

	const want = "line1\nline2\t\"quoted\""
	if got != want {
		t.Fatalf("system[2] text = %q, want %q", got, want)
	}

Copy link
Author

Choose a reason for hiding this comment

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

Agreed, strengthened to exact match. Fixed in d2474c0.

…st assertion

Address code review feedback:
- Replace manual string escaping with json.Marshal for robustness
- Use exact string match in special chars test instead of substring check
@shellus shellus changed the title fix(cloaking): preserve string-format system prompt in checkSystemInstructionsWithMode fix(cloaking): string-format system prompt silently dropped, causing lost user instructions Mar 9, 2026
@luispater
Copy link
Collaborator

Closing this PR in favor of #1992, which has already been merged. The same fix is now on main.

@luispater luispater closed this Mar 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants