Skip to content

Fix/29365 duplicate tool responses - #29400

Closed
abhashkumar9051 wants to merge 3 commits into
google-gemini:mainfrom
abhashkumar9051:fix/29365-duplicate-tool-responses
Closed

abhashkumar9051 wants to merge 3 commits into
google-gemini:mainfrom
abhashkumar9051:fix/29365-duplicate-tool-responses

Conversation

@abhashkumar9051

Copy link
Copy Markdown

Summary

Fixes duplicate functionResponse messages when resuming Gemini CLI sessions with -r.

Tool results could be persisted both in toolCalls[].result and as durable user messages. During session restoration, both copies could be replayed, causing duplicate function responses for the same function call ID.

This can cause strict Gemini-compatible backends to reject the resumed request because the number of function responses does not match the number of function calls.

Details

The fix makes durable user functionResponse records the preferred source of truth during session restoration.

Changes include:

  • convertSessionToClientHistory() now tracks functionResponse IDs already stored in user messages.
  • Tool results from toolCalls[].result are only regenerated when a durable response for the same call ID does not exist.
  • Duplicate functionResponse parts with the same call ID are filtered during history reconstruction.
  • Tool-call result synchronization now stores only the response matching the current call ID.
  • Parallel tool-call responses remain isolated and no longer inherit responses belonging to sibling calls.

Related Issues

Fixes #29365

How to Validate

Run the relevant test suites and verify that:

  • Sessions containing persisted tool results can be resumed without duplicate functionResponse parts.
  • Previously corrupted sessions containing repeated responses can be reconstructed safely.
  • Parallel tool calls keep their responses associated with the correct call IDs.
  • The Darwin PTY orphan-FD regression test correctly detects, closes, and returns the matching file descriptor.

Validation performed:

  • Targeted tests: 62 tests passed
  • ESLint: Passed
  • TypeScript typecheck: Passed
  • Regression tests: Passed
  • Parallel tool-call tests: Passed
  • Previously failing Darwin PTY test: Fixed and re-tested successfully

Pre-Merge Checklist

  • Added/updated tests (if needed)
  • Updated the existing Darwin PTY orphan-FD cleanup test by mocking realpathSync to make PTY path resolution deterministic.
  • The Darwin test initially failed with Expected: 12, Received: undefined; after the test setup was corrected, it correctly identifies FD 12, closes it, and returns 12.

@abhashkumar9051
abhashkumar9051 requested a review from a team as a code owner September 18, 2026 19:26
@google-cla

google-cla Bot commented Sep 18, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@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 an issue where Gemini CLI sessions resumed with the -r flag would generate duplicate functionResponse messages. By ensuring that durable user messages are treated as the source of truth and filtering out redundant or incorrect tool results during history reconstruction, the fix prevents backend rejection errors caused by mismatched function call and response counts. Additionally, it improves the isolation of parallel tool calls and stabilizes existing regression tests.

Highlights

  • Session Restoration Fix: Implemented logic to prioritize durable user functionResponse records over toolCalls results during session restoration to prevent duplicate function responses.
  • Duplicate Filtering: Added tracking for functionResponse IDs to filter out duplicates during history reconstruction and ensure only the matching response is used for each tool call.
  • Parallel Tool Call Isolation: Updated tool-call result synchronization to ensure parallel tool calls remain isolated and do not inherit responses belonging to sibling calls.
  • Test Improvements: Updated the Darwin PTY orphan-FD cleanup test by mocking realpathSync to ensure deterministic path resolution.
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/m A medium sized PR label Sep 18, 2026
@github-actions

Copy link
Copy Markdown

📊 PR Size: size/M

  • Lines changed: 219
  • Additions: +199
  • Deletions: -20
  • Files changed: 5

@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 modifies the session history conversion and recording synchronization logic to prevent replaying duplicate tool results and sibling call responses during parallel tool execution. However, the feedback highlights that the current implementation of filtering tool call results by matching ID discards multi-modal sibling parts (such as inlineData or text parts). The reviewer recommends consolidating the filtering logic into a shared utility function that preserves these sibling parts, and restoring the corresponding test cases and assertions in chatRecordingService.test.ts to ensure multi-modal tool outputs are not lost.

Comment on lines +1009 to +1016
const matchingPart = (turn.content.parts || []).find(
(candidate) => candidate.functionResponse?.id === callId,
);
if (
JSON.stringify(tc.result) !==
JSON.stringify(turn.content.parts)
matchingPart &&
JSON.stringify(tc.result) !== JSON.stringify([matchingPart])
) {
tc.result = turn.content.parts || [];
tc.result = [matchingPart];

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.

high

By using .find() and wrapping only the single matching functionResponse part in an array, any multi-modal sibling parts (such as inlineData or text parts returned by the tool) are completely discarded during history synchronization. This breaks multi-modal tool output preservation.

Instead, we should filter the parts to keep both the matching functionResponse and any non-functionResponse sibling parts. To avoid duplication and improve maintainability, this filtering logic should be consolidated into a shared utility function.

                const matchingParts = filterToolParts(turn.content.parts || [], callId);
                if (
                  matchingParts.length > 0 &&
                  JSON.stringify(tc.result) !== JSON.stringify(matchingParts)
                ) {
                  tc.result = matchingParts;
                  updated = true;
                }
References
  1. When adding new functionality, such as filtering, consolidate it with existing similar logic to avoid duplication and improve maintainability.

Comment on lines +232 to +236
functionResponseParts.push(
...ensurePartArray(toolCall.result).filter(
(part) => part.functionResponse?.id === toolCall.id,
),
);

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.

high

Filtering the toolCall.result array to only keep parts where part.functionResponse?.id === toolCall.id will discard any multi-modal sibling parts (such as inlineData or text parts) associated with the tool call. This causes multi-modal tool outputs to be lost when resuming a session.

We should update the filter to also preserve any non-functionResponse parts. To avoid duplication and improve maintainability, this filtering logic should be consolidated into a shared utility function.

                functionResponseParts.push(
                  ...filterToolParts(ensurePartArray(toolCall.result), toolCall.id),
                );
References
  1. When adding new functionality, such as filtering, consolidate it with existing similar logic to avoid duplication and improve maintainability.

});

it('should preserve multi-modal sibling parts during sync', async () => {
it('should sync only the matching function response during sync', async () => {

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.

high

Rename the test back to reflect that it is verifying the preservation of multi-modal sibling parts.

Suggested change
it('should sync only the matching function response during sync', async () => {
it('should preserve multi-modal sibling parts during sync', async () => {

Comment on lines +1443 to 1447
expect(result).toHaveLength(1);
expect(result[0].functionResponse!.response).toEqual({
output: maskedSnippet,
});
expect(result[1].inlineData).toBeDefined();
expect(result[1].inlineData!.mimeType).toBe('image/png');
});

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.

high

Since we should preserve multi-modal sibling parts (like inlineData) during history synchronization, we should restore the original assertions of this test to verify that the sibling inlineData part is not discarded.

      expect(result).toHaveLength(2);
      expect(result[0].functionResponse!.response).toEqual({
        output: maskedSnippet,
      });
      expect(result[1].inlineData).toBeDefined();
      expect(result[1].inlineData!.mimeType).toBe('image/png');
    });

@gemini-cli gemini-cli Bot added priority/p1 Important and should be addressed in the near term. area/core Issues related to User Interface, OS Support, Core Functionality labels Sep 18, 2026
@abhashkumar9051
abhashkumar9051 force-pushed the fix/29365-duplicate-tool-responses branch 2 times, most recently from 052fe11 to ff6559e Compare September 18, 2026 21:07
@abhashkumar9051
abhashkumar9051 force-pushed the fix/29365-duplicate-tool-responses branch from ff6559e to 5dac544 Compare September 18, 2026 21:18
@gemini-cli

gemini-cli Bot commented Sep 26, 2026

Copy link
Copy Markdown
Contributor

Hi there! Thank you for your interest in contributing to Gemini CLI.

To ensure we maintain high code quality and focus on our prioritized roadmap, we only guarantee review and consideration of pull requests for issues that are explicitly labeled as 'help wanted'.

This PR will be closed in 7 days if it remains without that designation. We encourage you to find and contribute to existing 'help wanted' issues in our backlog! Thank you for your understanding.

@gemini-cli

gemini-cli Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

This pull request is being closed as it has been open for 14 days without a 'help wanted' designation. We encourage you to find and contribute to existing 'help wanted' issues in our backlog! Thank you for your understanding.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core Issues related to User Interface, OS Support, Core Functionality priority/p1 Important and should be addressed in the near term. size/m A medium sized PR status/pr-nudge-sent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Resuming a session (-r) sends every tool result twice: toolCalls[].result and the recorded functionResponse turn are both replayed

1 participant