Skip to content

fix(core): skip merged function-response turns when finding the active loop - #28565

Merged
DavidAPierce merged 1 commit into
mainfrom
fix/thought-signature-coalesced-turns
Jul 28, 2026
Merged

fix(core): skip merged function-response turns when finding the active loop#28565
DavidAPierce merged 1 commit into
mainfrom
fix/thought-signature-coalesced-turns

Conversation

@adamfweidman

Copy link
Copy Markdown
Collaborator

Summary

Tool calls can be sent to the API without a thought signature, which the API
rejects with 400 INVALID_ARGUMENT. The bad turn stays in history, so once it
happens the session cannot recover. Skill activation triggers it on every use,
because that call is generated client-side and carries no signature of its own.

Regression from #28407, first shipped in v0.52.0-nightly.20260716. Not in
v0.51.0.

Details

ensureActiveLoopHasThoughtSignatures finds the start of the active loop by
looking for the last user turn containing text. Its comment already describes
the correct rule:

// ...the last user turn with a text message, i.e. that is not a function response.
if (content.role === 'user' && content.parts?.some((part) => part.text)) {

The code never checked for a function response. That was equivalent only while a
user turn was either pure text or pure function response.

#28407 added coalesceConsecutiveRoles, which merges adjacent same-role turns —
so a function response turn can now be merged with the prompt that follows it.
Starting the loop at such a turn starts it later than the API starts the turn,
so model turns in between are validated by the API but never receive a synthetic
signature.

The fix adds the missing condition, making the code match its comment and the
API's own turn boundary. One condition, one regression test.

Related Issues

Regression from #28407.

How to Validate

npm test -w @google/gemini-cli-core -- src/core/geminiChat.test.ts

The new test builds the merged functionResponse + text turn and asserts the
preceding call is signed. It fails on main
(expected undefined to be 'skip_thought_signature_validator') and passes with
this change.

Manually: send a prompt, let the model reply, then activate a skill with
/<skill-name> and send a follow-up. Before this change the follow-up returns
400; after it, it succeeds.

Pre-Merge Checklist

  • Updated relevant documentation and README (if needed)
  • Added/updated tests (if needed)
  • Noted breaking changes (if any)
  • Validated on required platforms/methods:
    • MacOS
      • npm run
      • npx
      • Docker
      • Podman
      • Seatbelt
    • Windows
      • npm run
      • npx
      • Docker
    • Linux
      • npm run
      • npx
      • Docker

…e loop

`ensureActiveLoopHasThoughtSignatures` located the start of the active
loop by looking for the last user turn containing text. Its comment
already described the correct rule ("i.e. that is not a function
response"), but the code never checked for one, which was equivalent
only while a user turn was either pure text or pure function response.

`coalesceConsecutiveRoles` merges adjacent same-role turns, so a function
response turn can now be merged with the prompt that follows it. Starting
the loop at such a turn starts it later than the API starts the turn, so
model turns in between are validated by the API but never get a synthetic
signature, and the request is rejected.

Tool calls made just before the merged turn are the ones affected; skill
activation hits this on every use because its call is client-generated
and carries no signature of its own.
@adamfweidman
adamfweidman requested a review from a team as a code owner July 28, 2026 19:06
@gemini-code-assist

Copy link
Copy Markdown
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 regression where the active loop detection logic was incorrectly identifying the start of a loop when user turns were merged with function responses. By explicitly filtering out turns containing function responses, the fix ensures that synthetic thought signatures are correctly applied to all necessary model turns, preventing API validation errors.

Highlights

  • Fix for active loop detection: Updated the ensureActiveLoopHasThoughtSignatures logic to correctly identify the start of the active loop by excluding user turns that contain function responses, preventing incorrect loop boundaries after turn merging.
  • Regression test added: Added a new test case in geminiChat.test.ts that simulates a merged function response and text turn to ensure the loop detection logic correctly signs preceding model turns.
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 the 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 counterproductive. 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.

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.

@github-actions github-actions Bot added the size/s A small PR label Jul 28, 2026
@github-actions

Copy link
Copy Markdown

📊 PR Size: size/S

  • Lines changed: 43
  • Additions: +41
  • Deletions: -2
  • Files changed: 2

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

Copy link
Copy Markdown
Contributor

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 updates the active loop detection logic in GeminiChat to skip user turns that contain both text and a functionResponse (which can occur when consecutive roles are coalesced). This prevents earlier model turns from being left unsigned while still validated by the API. A corresponding unit test has been added to verify this behavior. I have no feedback to provide as the changes are correct and well-tested.

@github-actions

Copy link
Copy Markdown

Size Change: +53 B (0%)

Total Size: 35.2 MB

Filename Size Change
./bundle/chunk-2Q5BENWG.js 0 B -661 kB (removed) 🏆
./bundle/chunk-CLMUEPXR.js 0 B -13 kB (removed) 🏆
./bundle/chunk-HDFVMJNL.js 0 B -49.2 kB (removed) 🏆
./bundle/chunk-N2ZCHQN4.js 0 B -3.65 MB (removed) 🏆
./bundle/chunk-NB6ELFZS.js 0 B -3.77 kB (removed) 🏆
./bundle/chunk-S4ILPFFB.js 0 B -16.6 MB (removed) 🏆
./bundle/chunk-SAZHXKII.js 0 B -19.5 kB (removed) 🏆
./bundle/chunk-XFKVNXW2.js 0 B -3.43 kB (removed) 🏆
./bundle/core-LEJKESNK.js 0 B -50.1 kB (removed) 🏆
./bundle/devtoolsService-C5DJEPYF.js 0 B -147 kB (removed) 🏆
./bundle/gemini-TT3UQ2ZG.js 0 B -590 kB (removed) 🏆
./bundle/interactiveCli-LLY75THI.js 0 B -1.3 MB (removed) 🏆
./bundle/liteRtServerManager-3TDJ2LR3.js 0 B -2.08 kB (removed) 🏆
./bundle/oauth2-provider-YHEVNVYK.js 0 B -9.12 kB (removed) 🏆
./bundle/chunk-GX6VL2YM.js 661 kB +661 kB (new file) 🆕
./bundle/chunk-HZQK7JWB.js 19.5 kB +19.5 kB (new file) 🆕
./bundle/chunk-L4KH74GK.js 49.2 kB +49.2 kB (new file) 🆕
./bundle/chunk-N5VHGAZ3.js 3.43 kB +3.43 kB (new file) 🆕
./bundle/chunk-OSILOTP6.js 16.6 MB +16.6 MB (new file) 🆕
./bundle/chunk-RMD37W3M.js 3.65 MB +3.65 MB (new file) 🆕
./bundle/chunk-XEN5K7PG.js 13 kB +13 kB (new file) 🆕
./bundle/chunk-XFU5NWK6.js 3.77 kB +3.77 kB (new file) 🆕
./bundle/core-32SSKVSV.js 50.1 kB +50.1 kB (new file) 🆕
./bundle/devtoolsService-ZP23WP3A.js 147 kB +147 kB (new file) 🆕
./bundle/gemini-4GDAQCTC.js 590 kB +590 kB (new file) 🆕
./bundle/interactiveCli-PMY4HWXJ.js 1.3 MB +1.3 MB (new file) 🆕
./bundle/liteRtServerManager-IERXK22O.js 2.08 kB +2.08 kB (new file) 🆕
./bundle/oauth2-provider-QARQT32L.js 9.12 kB +9.12 kB (new file) 🆕
ℹ️ View Unchanged
Filename Size Change
./bundle/bundled/third_party/index.js 8 MB 0 B
./bundle/chunk-34MYV7JD.js 2.45 kB 0 B
./bundle/chunk-5AUYMPVF.js 858 B 0 B
./bundle/chunk-5PS3AYFU.js 1.18 kB 0 B
./bundle/chunk-664ZODQF.js 124 kB 0 B
./bundle/chunk-DAHVX5MI.js 206 kB 0 B
./bundle/chunk-IUUIT4SU.js 56.5 kB 0 B
./bundle/chunk-L5V3KIDT.js 1.62 kB 0 B
./bundle/chunk-TUDYL3X4.js 40.3 kB 0 B
./bundle/cleanup-GZRDBGX7.js 0 B -902 B (removed) 🏆
./bundle/devtools-TYCPOPV3.js 683 kB 0 B
./bundle/events-XB7DADIJ.js 418 B 0 B
./bundle/examples/hooks/scripts/on-start.js 188 B 0 B
./bundle/examples/mcp-server/example.js 1.43 kB 0 B
./bundle/gemini.js 5.38 kB 0 B
./bundle/getMachineId-bsd-TXG52NKR.js 1.55 kB 0 B
./bundle/getMachineId-darwin-7OE4DDZ6.js 1.55 kB 0 B
./bundle/getMachineId-linux-SHIFKOOX.js 1.34 kB 0 B
./bundle/getMachineId-unsupported-5U5DOEYY.js 1.06 kB 0 B
./bundle/getMachineId-win-6KLLGOI4.js 1.72 kB 0 B
./bundle/https-proxy-agent-AVGR4LHR.js 490 B 0 B
./bundle/multipart-parser-E7RMVJWU.js 11.7 kB 0 B
./bundle/multipart-parser-KPBZEGQU.js 11.7 kB 0 B
./bundle/sandbox-macos-permissive-open.sb 5.29 kB 0 B
./bundle/sandbox-macos-permissive-proxied.sb 5.58 kB 0 B
./bundle/sandbox-macos-restrictive-open.sb 3.3 kB 0 B
./bundle/sandbox-macos-restrictive-proxied.sb 3.5 kB 0 B
./bundle/sandbox-macos-strict-open.sb 4.75 kB 0 B
./bundle/sandbox-macos-strict-proxied.sb 4.96 kB 0 B
./bundle/src-65GKNWUJ.js 45.4 kB 0 B
./bundle/src-U45KTUYT.js 45.7 kB 0 B
./bundle/src-XZYPU6PJ.js 352 kB 0 B
./bundle/start-ARLN4KUL.js 0 B -622 B (removed) 🏆
./bundle/tree-sitter-7U6MW5PS.js 274 kB 0 B
./bundle/tree-sitter-bash-34ZGLXVX.js 1.84 MB 0 B
./bundle/worker/worker-entry.js 363 kB 0 B
./bundle/cleanup-6JBR3S7J.js 902 B +902 B (new file) 🆕
./bundle/start-JWC2ORH2.js 622 B +622 B (new file) 🆕

compressed-size-action

@DavidAPierce DavidAPierce left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM.

@DavidAPierce
DavidAPierce added this pull request to the merge queue Jul 28, 2026
Merged via the queue into main with commit d29268d Jul 28, 2026
34 checks passed
@DavidAPierce
DavidAPierce deleted the fix/thought-signature-coalesced-turns branch July 28, 2026 20:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s A small PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants