google-gemini / google-gemini/gemini-cli

ACP session/load erases the session it is asked to load (0.53.0)

Open
#28,775 1 comment 0 reactions 0 assignees View on GitHub
status/need-triage
Dominant language
TypeScript
Stars
107k
Forks
14.6k
Avg merge
2d 3h
Merged PRs (30d)
45

Description

# ACP `session/load` erases the session it is asked to load

**Version:** 0.53.0 (also reproduced through the bundled `gemini --acp`)
**Mode:** ACP (`gemini --acp`), driven by an external ACP client over stdio

## Summary

`session/load` fails with

```
-32603 Internal error
data.details: "No previous sessions found for this project."
```

for a session `session/new` created minutes earlier, in the same project
directory, in the same sandbox — and the failure is caused by the load itself.
The session file is on disk and `gemini --list-sessions` lists it right up until
the moment a load is attempted; afterwards it is gone from the listing for good.

## Mechanism

`AcpSessionManager.loadSession` calls `initializeSessionConfig(sessionId, …)`
**before** `new SessionSelector(config.storage).resolveSession(sessionId)`.

That config's `ChatRecordingService.initialize()` runs with no
`resumedSessionData` (the resume happens later, via `geminiClient.resumeChat`),
so it takes its *new session* branch. That branch derives the conversation file
name from `SESSION_FILE_PREFIX + + `
— the same name the original turn wrote, whenever the load lands in the same
wall-clock minute — and appends a fresh header plus a `$set` whose `messages`
array contains only the `` bootstrap message.

`loadConversationRecord` treats a `$set` carrying `messages` as a **replacement**:

```js
if (hasProperty(record.$set, "messages") && Array.isArray(record.$set.messages)) {
messagesMap.clear(); // and messageIds/messageKinds under metadataOnly
```

and `isResumableMessageRecord` rejects user content starting with
`` via `isIgnoredUserContent`. So the conversation the client
asked to load now reports `hasResumableContent: false`, is filtered out of
`getAllSessionFiles`, leaves `listSessions()` empty, and `findSession` throws
`SessionError.noSessionsFound()` — reported over ACP as `-32603`.

The transcript is still in the file, immediately above the line that erased it.

## Reproduction

1. `gemini --acp` in a git-initialised working directory.
2. `initialize` → `authenticate` (`gemini-api-key`) → `session/new` →
`session/prompt` ("Reply with exactly: ECHO-5"). Let the process exit.
3. Confirm the session exists: `gemini --list-sessions` lists it, and
`$HOME/.gemini/tmp//chats/session--.jsonl` contains
the user and gemini messages.
4. Start `gemini --acp` again **within the same wall-clock minute** and send
`session/load` with that session id.

Result: `-32603` "No previous sessions found for this project", and the session
file now ends with a header and `$set {"messages":[{…"…"}]}`.
`gemini --list-sessions` no longer lists it either.

## Why it looks intermittent

The file name is bucketed by minute, so the outcome depends on the wall clock:

| load happens | file the recorder opens | result |
|---|---|---|
| same minute as the previous turn | the session's own file | fails, session destroyed |
| a later minute | a sibling file | succeeds |

A second consecutive load failed for us even with a minute between every turn,
and afterwards the older session files for that project were gone.

## Suggested fixes

1. Resolve the session **before** constructing the config, or pass
`resumedSessionData` so `ChatRecordingService` takes its resume branch rather
than its new-session branch.
2. Don't let a new-session recorder open an existing file: the name collides
whenever a session id is reused within a minute.

## Unrelated, same code path

`loadSession` streams the replay as a floating promise:

```js
// eslint-disable-next-line @typescript-eslint/no-floating-promises
session.streamHistory(sessionData.messages);
```

ACP requires the agent to replay "before responding" to `session/load`. Because
the response goes out first, a client that stops discarding replay on the
response — the natural reading of the spec — persists the whole history again on
every turn. `await` would fix it.

Contributor guide

Open the contributing guide

Research direction

Start at AcpSessionManager.loadSession and trace initializeSessionConfig, ChatRecordingService.initialize, and SessionSelector.resolveSession; reproduce with the documented ACP session/new and session/load sequence. Verify that loading no longer removes the session or hides its transcript, and that history replay completes before the load response; inspect loadConversationRecord and isResumableMessageRecord for the persistence behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, cli
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.