google-gemini/gemini-cli · error
Circular symlink detected
Error message
Circular symlink detected
What it means
safeResolveToRealPath walks up the path chain (toward the filesystem root), remembering visited components in a Set keyed by normalized (lowercased on Windows) paths. If the walk revisits a component, the symlink structure is a loop and resolution would never terminate, so it throws. This protects path resolution from malicious or broken circular symlinks.
Solutions
- Inspect the path chain with 'namei -l <path>' or 'readlink -f <path>' and delete/replace the cyclical symlink.
- Recreate the affected link as a plain directory or one pointing to a real, acyclic target.
- Restore the .gemini directory from a known-good state (e.g. remove and let the CLI reinitialize).
Example fix
// before $ ln -s ~/config ~/.gemini # where ~/config -> ~/.gemini (loop) // after $ rm ~/.gemini && mkdir ~/.gemini
Defensive patterns
Strategy: try-catch
Validate before calling
try {
fs.realpathSync(p);
} catch {
throw new Error(`path ${p} has an unresolvable (possibly circular) symlink`);
} Try / catch
try {
const real = safeResolveToRealPath(p);
} catch (e) {
if (e.message === 'Circular symlink detected') {
// surface a fix hint: run namei -l <p> and remove the loop
}
} Prevention
- Audit dotfiles that symlink ~/.gemini; never create links that point into their own subtree.
- Use readlink -f / namei -l to verify paths after restoring from backups.
- Prefer copying directories over symlinking for CLI config paths.
When it happens
Trigger: safeResolveToRealPath (used by resolvedPath/home/normalized/geminiDirOnHost) walking a path whose ancestor chain contains a symlink cycle, e.g. a symlink pointing to its own parent or a symlinked directory that loops back.
Common situations: A broken dotfiles setup where ~/.gemini or a config directory is a symlink into itself; restoring backups that recreated recursive symlinks; container/host shared paths with cyclical links.
Related errors
- Access to forbidden path is denied
- ENOENT
- Failed to mount workspace into LXC container
- Missing mount path ' ' listed in SANDBOX_MOUNTS
- Running sandbox from a sensitive host directory
AI-assisted analysis of google-gemini/gemini-cli@6a466a7e2f (2026-09-16).
Data as JSON: /api/errors/8df21d4b58a4f8f8.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/utils/sandboxUtils.ts:115
return true;
}
return false;
}
/**
* Resolves a path to its real path, falling back to path.resolve if it does not exist (ENOENT).
* Rethrows unrecoverable errors so callers can fail closed.
*/
function safeResolveToRealPath(targetPath: string): string {
let current = path.resolve(targetPath);
const parts: string[] = [];
const visited = new Set<string>();
while (current && current !== path.dirname(current)) {
const visitKey =
os.platform() === 'win32' ? current.toLowerCase() : current;
if (visited.has(visitKey)) {
throw new Error('Circular symlink detected');
}
visited.add(visitKey);
try {
const real = resolveToRealPath(current);
return path.resolve(real, ...parts.slice().reverse());
} catch (err: unknown) {
if (isRecord(err) && err['code'] === 'ENOENT') {
try {
const stat = fs.lstatSync(current);
if (stat?.isSymbolicLink?.()) {
const target = fs.readlinkSync(current);
current = path.resolve(path.dirname(current), target);
continue;
}
} catch (lstatErr: unknown) {
if (!isRecord(lstatErr) || lstatErr['code'] !== 'ENOENT') {
throw lstatErr;View on GitHub (pinned to 6a466a7e2f)