google-gemini/gemini-cli · error · Error
EEXIST
EEXIST
Error message
Slug ${slug} is already owned by ${owner} What it means
EEXIST 'Slug X is already owned by Y' is thrown by ensureOwnershipMarkers when a project-slug directory's PROJECT_ROOT_FILE marker already exists and points to a DIFFERENT normalized project path. The project registry uses these markers to map short slugs to absolute project paths; a mismatch means another project claimed the slug first.
Source
Thrown at packages/core/src/config/projectRegistry.ts:391
slug: string,
projectPath: string,
): Promise<void> {
const normalizedProject = this.normalizePath(projectPath);
for (const baseDir of this.baseDirs) {
const slugDir = path.join(baseDir, slug);
if (!fs.existsSync(slugDir)) {
await fs.promises.mkdir(slugDir, { recursive: true });
}
const markerPath = path.join(slugDir, PROJECT_ROOT_FILE);
if (fs.existsSync(markerPath)) {
const owner = (await fs.promises.readFile(markerPath, 'utf8')).trim();
if (this.normalizePath(owner) === normalizedProject) {
continue;
}
// Collision!
const error = Object.assign(
new Error(`Slug ${slug} is already owned by ${owner}`),
{ code: 'EEXIST' },
);
throw error;
}
// Use flag: 'wx' to ensure atomic creation
try {
await fs.promises.writeFile(markerPath, normalizedProject, {
encoding: 'utf8',
flag: 'wx',
});
} catch (e: unknown) {
if (isNodeError(e) && e.code === 'EEXIST') {
// Re-verify ownership in case we just lost a race
const owner = (await fs.promises.readFile(markerPath, 'utf8')).trim();
if (this.normalizePath(owner) === normalizedProject) {
continue;
}
}
throw e;View on GitHub (pinned to 5024443c72)
Solutions
- Let the caller retry — getOrAssignSlug already advances to the next candidate on EEXIST; the error should be rare to surface.
- Delete the stale marker directory (<baseDir>/<slug>/PROJECT_ROOT_FILE) for the old owner if the project was renamed/moved.
- Rename one project so its slugify() output is unique.
Example fix
# before: marker owned by /old/path rm -rf ~/.gemini/registry/my-app # after: next setupUser call reclaims the slug cleanly
Defensive patterns
Strategy: try-catch
Validate before calling
// pre-check ownership before ensureOwnershipMarkers
for (const base of baseDirs) {
const marker = path.join(base, slug, PROJECT_ROOT_FILE);
if (fs.existsSync(marker) && fs.readFileSync(marker,'utf8').trim() !== normalize(projectPath)) {
return pickNextCandidate();
}
} Type guard
function isSlugCollision(e: unknown): boolean {
return isNodeError(e) && (e as NodeJS.ErrnoException).code === 'EEXIST'
|| (e instanceof Error && e.message.includes('already owned by'));
} Try / catch
try { await registry.ensureOwnershipMarkers(slug, projectPath); }
catch (e) {
if (isSlugCollision(e)) { /* try next candidate slug */ continue; }
throw e;
} Prevention
- Garbage-collect registry markers for renamed/moved projects.
- Give sibling projects distinct directory names so slugify() differs.
When it happens
Trigger: ensureOwnershipMarkers(slug, projectPath) reads markerPath; reads owner from it; normalizePath(owner) !== normalizePath(projectPath) -> throws Error with code:'EEXIST' at line 389-393. The caller (getOrAssignSlug) catches code==='EEXIST' or message includes 'already owned by' and retries the next candidate slug.
Common situations: Two sibling projects with similar directory names slugify to the same string (e.g. 'my-app' and 'My App'); stale marker left by a moved/renamed project directory; race when two CLI processes onboard different projects concurrently.
Related errors
- ENOENT
- Workspace path ${resolvedPath} does not exist
- Workspace path ${resolvedPath} is not a directory
- tar.c command failed to create ${tmpArchiveFile}
- The path "${trimmedPath}" is not a directory.
AI-assisted analysis of google-gemini/gemini-cli@5024443c72 (2026-08-12).
Data as JSON: /api/errors/93fe4d047f4a7aec.
Report an issue: GitHub.