google-gemini / google-gemini/gemini-cli
ACP: session/load in the same UTC minute as session/new fails with "No previous sessions found" — and durably destroys the session's resumability
- Dominant language
- TypeScript
- Stars
- 107k
- Forks
- 14.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 45
Description
> [!NOTE]
> **Updated 2026-08-09 — root cause identified; original framing corrected.** The original report claimed a crashed session is "never persisted". That's wrong: persistence is fine (every record is written synchronously, and the conversation file survives any kill). The real defect is in the **load path**: `session/load` eagerly re-initializes chat recording for the requested session id *before* resolving it, appending a fresh metadata header + `$set {messages: []}` checkpoint to `session--.jsonl`. When the load runs in the **same UTC minute** the session was created, that append lands in the session's own conversation file; the checkpoint buries the conversation in the fold, `hasResumableContent` flips false, the session is filtered from the listing, and the load fails with `No previous sessions found for this project` — **after durably destroying the session's resumability**. No crash is required (it reproduces after a clean exit). Full mechanism with source refs at `cf22ac7`, a three-variant repro on 0.54.0, and the likely unification with #27913: [see this comment](https://github.com/google-gemini/gemini-cli/issues/28693#issuecomment-5233382273).
### What happened
When `gemini` runs as an ACP agent, `initialize` advertises `agentCapabilities.loadSession: true` — but a `session/load` issued in the same UTC minute as the session's `session/new` fails with:
```
No previous sessions found for this project
```
and durably poisons the session file, so every later `session/load` for that id fails too. Automated ACP clients (an editor reconnecting right after a crash, scripted harnesses) land in this window routinely; a human retrying minutes later lands in a different minute and sees loads succeed, which makes the failure look intermittent or environment-dependent.
### Version
- Originally observed on `gemini` 0.53.0; root-caused and reproduced on 0.54.0, macOS/arm64, driven over stdio by a minimal ACP client. Mechanism present in source at `cf22ac7`.
### Steps to reproduce (no crash or kill required)
1. Start gemini in ACP mode; `initialize` → `loadSession: true`.
2. `session/new` ({ cwd, mcpServers: [] }) → `sessionId`.
3. Run one or more `session/prompt` turns to completion.
4. End the process — cleanly (close stdin, exit 0) or by SIGKILL of the process tree; both reproduce.
5. Spawn a fresh gemini ACP process; `initialize`; `session/load` ({ sessionId, cwd, mcpServers: [] }) — **within the same UTC minute as step 2**.
### Expected
`session/load` finds the session (its file is on disk with all turns) and replays the conversation per the ACP spec — and, being a read operation, does not modify the session it is resolving.
### Actual
Step 5 fails with `No previous sessions found for this project`, and the session's `session--.jsonl` gains a trailing metadata header + `$set {messages: []}` checkpoint written by the load call itself, permanently burying the conversation in the fold (the turn records remain physically present in the file, before the poison pair). Later `session/load` calls — in any minute — keep failing, each leaving an additional context-only junk file.
### Historical note on the original report
The original report reproduced this via SIGKILL of the whole process tree mid-turn and framed it as missing crash persistence. The tree-kill observation itself stands — each launch self-re-execs a sandbox wrapper, so killing only the spawned pid orphans the real agent, and probes must kill the tree — but neither the kill nor its timing is load-bearing for this bug: the probe's new → turns → kill → load sequence simply always landed inside one minute.
### Related
#27913 is very likely the same root cause observed one release earlier: it was filed against 0.46.0, before `3a13b8eeb` (#27770, first shipped in v0.47.0) added the `hasResumableContent` filter. Pre-filter, the poisoned fold still lists, so `session/load` "succeeds" — replaying exactly one `user_message_chunk` (the ``, which `streamHistory` doesn't filter) while the model's restored history is empty (`convertSessionToClientHistory` filters it out). Post-filter, the identical on-disk state reports `No previous sessions found` instead.
Contributor guide
Research direction
Start at the ACP session/load entry point and inspect how it invokes chat recording before resolving the requested session. Trace hasResumableContent, streamHistory, and convertSessionToClientHistory against the same-minute JSONL reproduction. Done means session/load resolves and replays the existing conversation without appending a metadata or context-only checkpoint or damaging resumability.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend-api-design, cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100