Repository: google-gemini/gemini-cli (TypeScript)

Issue:
Bug: Non-interactive sessions do not log tool calls in session history
### What happened?

When running the Gemini CLI in a non-interactive session (using the `-p` flag), tool call information is not being recorded in the automatically saved session history JSON files. However, when running in an interactive session (with the `-i` flag), the tool calls are logged correctly.

For example, when running the following command:

```sh
gemini -e none --allowed-tools=run_shell_command --allowed-tools="run_shell_command(wc)" -p "Use wc to count the lines in poem.md"
```

The command executes successfully, but the corresponding session log file is missing the toolCalls section, even though a tool was used. Here is the content of the session file:
```json
{
  "sessionId": "2d930cce-6744-4551-a764-1a2d085b84f9",
  "projectHash": "a7a86deed79eae624f96947bf41d7fff47ef3939dc916d899817b2532bd7821d",
  "startTime": "2025-10-10T20:48:21.244Z",
  "lastUpdated": "2025-10-10T20:49:00.880Z",
  "messages": [
    {
      "id": "4442deb0-5157-4b1b-8126-e75402df2858",
      "timestamp": "2025-10-10T20:48:21.245Z",
      "type": "user",
      "content": "Use wc to count the lines in poem.md"
    },
    {
      "id": "90ac2c87-4640-4c0a-9875-c912c9038c7e",
      "timestamp": "2025-10-10T20:49:00.880Z",
      "type": "gemini",
      "content": "5 poem.md",
      "thoughts": [
        {
          "subject": "Defining the Command",
          "description": "I've realized I need to use `wc` to count lines, which requires a command string. The user wants the line count of `poem.md`, so I'll incorporate that file name into the command. The `run_shell_command` tool is the vehicle I'll use to execute this.",
          "timestamp": "2025-10-10T20:48:24.329Z"
        },
        {
          "subject": "Executing the Command",
          "description": "I've got the command figured out: `wc -l poem.md`. Using `wc` directly is the most straightforward route, and `-l` is key for line counting.  I'm ready to execute it with the `run_shell_command` tool.",
          "timestamp": "2025-10-10T20:48:25.751Z"
        }
      ],
      "tokens": {
        "input": 11578,
        "output": 4,
        "cached": 8155,
        "thoughts": 0,
        "tool": 0,
        "total": 11582
      },
      "model": "gemini-2.5-pro"
    }
  ]
}
```

### What did you expect to happen?


### What did you expect to happen?

```markdown
I expected the session history file for the non-interactive session to contain a `toolCalls` field within the Gemini message, detailing the tool that was executed, similar to what is logged for interactive sessions. This is a regression, as this information was previously available.


### Client information

<details>
<summary>Client Information</summary>

Run `gemini` to enter the interactive CLI, then run the `/about` command.

```console
> /about

╭───────────────────────────────────────────────────────────────────────────────────╮
│                                                                                   │
│ About Gemini CLI                                                                  │
│                                                                                   │
│ CLI Version                 0.10.0-nightly.20251007.c195a9aa-git.c82c2c2b         │
│ Git Commit                  c82c2c2b                                              │
│ Model                       gemini-2.5-pro                                        │
│ Sandbox                     no sandbox                                            │
│ OS                          linux                                                 │
│ Auth Method                 gemini-api-key                                        │
│                                                                                   │
╰───────────────────────────────────────────────────────────────────────────────────╯

```

</details>

### Login information

_No response_

### Anything else we need to know?

This issue appears to be a regression. As noted in [issue #3882](https://github.com/google-gemini/gemini-cli/issues/3882#issuecomment-3392249831), the automatic saving of chats in interactive sessions works as expected, but for non-interactive chats, the tool call information is missing. This is a pretty substantial bug that hinders the ability to analyze tool usage in non-interactive sessions.



Discussion: @sniemczyk-google I have a PR in: #10951.



Fix (patch):
diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts
index 9a5e5fad09e..54f9eb909ea 100644
--- a/packages/cli/src/nonInteractiveCli.ts
+++ b/packages/cli/src/nonInteractiveCli.ts
@@ -4,7 +4,11 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
-import type { Config, ToolCallRequestInfo } from '@google/gemini-cli-core';
+import type {
+  Config,
+  ToolCallRequestInfo,
+  CompletedToolCall,
+} from '@google/gemini-cli-core';
 import { isSlashCommand } from './ui/utils/commandUtils.js';
 import type { LoadedSettings } from './config/settings.js';
 import {
@@ -132,12 +136,17 @@ export async function runNonInteractive(
 
         if (toolCallRequests.length > 0) {
           const toolResponseParts: Part[] = [];
+          const completedToolCalls: CompletedToolCall[] = [];
+
           for (const requestInfo of toolCallRequests) {
-            const toolResponse = await executeToolCall(
+            const completedToolCall = await executeToolCall(
               config,
               requestInfo,
               abortController.signal,
             );
+            const toolResponse = completedToolCall.response;
+
+            completedToolCalls.push(completedToolCall);
 
             if (toolResponse.error) {
               handleToolError(
@@ -155,6 +164,20 @@ export async function runNonInteractive(
               toolResponseParts.push(...toolResponse.responseParts);
             }
           }
+
+          // Record tool calls with full metadata before sending responses to Gemini
+          try {
+            const currentModel =
+              geminiClient.getCurrentSequenceModel() ?? config.getModel();
+            geminiClient
+              .getChat()
+              .recordCompletedToolCalls(currentModel, completedToolCalls);
+          } catch (error) {
+            console.error(
+              `Error recording completed tool call information: ${error}`,
+            );
+          }
+
           currentMessages = [{ role: 'user', parts: toolResponseParts }];
         } else {
           if (config.getOutputFormat() === OutputFormat.JSON) {
diff --git a/packages/core/src/agents/executor.ts b/packages/core/src/agents/executor.ts
index e81a19a0aad..9bc5b6ba854 100644
--- a/packages/core/src/agents/executor.ts
+++ b/packages/core/src/agents/executor.ts
@@ -534,7 +534,7 @@ export class AgentExecutor<TOutput extends z.ZodTypeAny> {
 
       // Create a promise for the tool execution
       const executionPromise = (async () => {
-        const toolResponse = await executeToolCall(
+        const { response: toolResponse } = await executeToolCall(
           this.runtimeContext,
           requestInfo,
           signal,
diff --git a/packages/core/src/core/nonInteractiveToolExecutor.ts b/packages/core/src/core/nonInteractiveToolExecutor.ts
index 67407230b59..e10988cfa61 100644
--- a/packages/core/src/core/nonInteractiveToolExecutor.ts
+++ b/packages/core/src/core/nonInteractiveToolExecutor.ts
@@ -4,12 +4,11 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
-import type {
-  ToolCallRequestInfo,
-  ToolCallResponseInfo,
-  Config,
-} from '../index.js';
-import { CoreToolScheduler } from './coreToolScheduler.js';
+import type { ToolCallRequestInfo, Config } from '../index.js';
+import {
+  CoreToolScheduler,
+  type CompletedToolCall,
+} from './coreToolScheduler.js';
 
 /**
  * Executes a single tool call non-interactively by leveraging the CoreToolScheduler.
@@ -18,14 +17,14 @@ export async function executeToolCall(
   config: Config,
   toolCallRequest: ToolCallRequestInfo,
   abortSignal: AbortSignal,
-): Promise<ToolCallResponseInfo> {
-  return new Promise<ToolCallResponseInfo>((resolve, reject) => {
+): Promise<CompletedToolCall> {
+  return new Promise<CompletedToolCall>((resolve, reject) => {
     new CoreToolScheduler({
       config,
       getPreferredEditor: () => undefined,
       onEditorClose: () => {},
       onAllToolCallsComplete: async (completedToolCalls) => {
-        resolve(completedToolCalls[0].response);
+        resolve(completedToolCalls[0]);
       },
     })
       .schedule(toolCallRequest, abortSignal)
diff --git a/packages/core/src/core/subagent.ts b/packages/core/src/core/subagent.ts
index 15cf5af9108..48fd7526948 100644
--- a/packages/core/src/core/subagent.ts
+++ b/packages/core/src/core/subagent.ts
@@ -580,11 +580,13 @@ export class SubAgentScope {
           error: undefined,
         };
       } else {
-        toolResponse = await executeToolCall(
-          this.runtimeContext,
-          requestInfo,
-          abortController.signal,
-        );
+        toolResponse = (
+          await executeToolCall(
+            this.runtimeContext,
+            requestInfo,
+            abortController.signal,
+          )
+        ).response;
       }
 
       if (toolResponse.error) {