google-gemini / google-gemini/gemini-cli
ACP session/load erases the session it is asked to load (0.53.0)
- 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
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