-
Notifications
You must be signed in to change notification settings - Fork 239
Description
Description
Starting with Gemini CLI 0.33.0, gemini --version (and any gemini command) triggers a concurrent startup race condition that produces:
Failed to save project registry to /home/runner/.gemini/projects.json: Error: ENOENT: no such file or directory, rename '/home/runner/.gemini/projects.json.tmp' -> '/home/runner/.gemini/projects.json'
Root Cause
In gemini.tsx, the CLI runs two cleanup functions concurrently:
await Promise.all([
cleanupCheckpoints(), // index 0
cleanupToolOutputFiles(settings), // index 1
]);Both create separate Storage → ProjectRegistry instances targeting the same ~/.gemini/projects.json. When the file doesn't exist, both call ProjectRegistry.save() without locking (the proper-lockfile lock in getShortId() only engages after the initial save). Both write to the same projects.json.tmp path:
- A:
writeFile('projects.json.tmp')→ succeeds - B:
writeFile('projects.json.tmp')→ overwrites A's file - A:
rename('projects.json.tmp', 'projects.json')→ succeeds, tmp file gone - B:
rename('projects.json.tmp', 'projects.json')→ ENOENT — A already renamed it
Workaround
Pre-seed ~/.gemini/projects.json with {"projects":{}} before running any gemini command. This causes both concurrent callers to skip the unguarded initial save() and go straight to proper-lockfile's lock(), which properly serializes access.
mkdir -p "${HOME}/.gemini"
if [[ ! -f "${HOME}/.gemini/projects.json" ]]; then
echo '{"projects":{}}' > "${HOME}/.gemini/projects.json"
fiUpstream Fix
The ideal fix would be in Gemini CLI itself — either share a single Storage/ProjectRegistry instance across both cleanup calls, or use a unique tmp file name (e.g., with PID or random suffix) in ProjectRegistry.save().
Environment
- Gemini CLI version: 0.33.0
- Runner: GitHub-hosted (Ubuntu 24.04)
- Node.js: 20.20.0