Skip to content

Daemon fails with ENOTSUP on ExFAT / external volumes (no tmpdir fallback for unsupported socket) #997

Closed

Description

@zengwenliang416

Summary

When a project lives on an ExFAT-formatted external volume (e.g. /Volumes/zwl/xjp/my-project), the daemon fails to start because ExFAT does not support Unix domain sockets. The listen() call throws ENOTSUP.

The existing code in daemon-paths.ts already has a tmpdir fallback for long socket paths (> 100 chars), but there is no fallback for filesystems that don't support sockets at all.

Error

✗ Failed to start server: listen ENOTSUP: operation not supported on socket /Volumes/zwl/xjp/my-project/.codegraph/daemon.sock

(daemon.log keeps accumulating this error on every daemon spawn attempt.)

Environment

  • CodeGraph version: 1.1.1
  • OS: macOS 26.0.1 (Darwin 25.0.0, Apple Silicon)
  • Filesystem: ExFAT (external USB drive)
  • Socket path length: 49 chars (well under the 100-char limit, so the tmpdir fallback is not triggered)

Root Cause

In daemon-paths.js, getDaemonSocketPath() only falls back to os.tmpdir() when inProject.length > POSIX_SOCKET_PATH_LIMIT:

function getDaemonSocketPath(projectRoot) {
    const inProject = path.join(getCodeGraphDir(projectRoot), 'daemon.sock');
    if (inProject.length <= POSIX_SOCKET_PATH_LIMIT)
        return inProject;        // ← ExFAT hits this branch, then listen() fails
    return path.join(os.tmpdir(), `codegraph-${projectHash(projectRoot)}.sock`);
}

The daemon's start() method (daemon.js) catches the ENOTSUP from linkSync (pid lockfile) and falls back to renameSync, but there is no equivalent fallback for the socket listen() call — it just cleans up and re-throws, killing the daemon.

Suggested Fix

When server.listen() fails with ENOTSUP (or ENOTSOCK / EACCES), retry with a tmpdir-based socket path — the same path getDaemonSocketPath already computes for the long-path case:

// In Daemon.start(), after the first listen fails:
if (err.code === 'ENOTSUP' || err.code === 'ENOTSOCK') {
    this.socketPath = path.join(os.tmpdir(), `codegraph-${projectHash(projectRoot)}.sock`);
    // retry listen on tmpdir path
}

Or proactively detect non-socket-capable filesystems in getDaemonSocketPath() itself.

Impact

  • Daemon (background auto-sync): broken — no automatic incremental indexing on file changes.
  • MCP tool (codegraph_explore): works fine (in-process, no daemon needed).
  • prompt-hook: works fine (in-process).
  • Manual codegraph index/sync: works fine.

So the functional impact is limited to losing automatic background indexing — users must run codegraph sync . manually after significant changes. But the repeated error in daemon.log is noisy.

Workaround

Run codegraph index . or codegraph sync . manually to refresh the index.

Activity

  1. colbymchenry commented on Jun 27, 2026

    @colbymchenry
    Owner

    Fixed in #1022 — merged to main, ships in the next release.

    Thanks for the unusually precise report; it made this fast to reproduce. Reproducing it on a real ExFAT volume actually turned up a second failure your logs hadn't reached yet: before the socket listen() runs, the daemon's lockfile step hard-links a temp file onto .codegraph/daemon.pid, and ExFAT/FAT don't support hard links either — so that throws first. Both are fixed now:

    • Lockfile: falls back to an exclusive O_EXCL create when the filesystem has no hard links.
    • Socket: the daemon relocates its socket to the system temp directory when the project's filesystem can't host one, and the proxy follows it there automatically — no config needed.

    Rather than special-casing ExFAT's specific error code, the fix triggers on the capability failure generically. That turned out to matter: the three OSes report three different errnos for the same gap — macOS ENOTSUP, Linux EPERM, Windows EISDIR — so it's validated end-to-end on real ExFAT/FAT volumes across macOS, Linux, and Windows.

    Once this releases you won't need the manual codegraph sync workaround — background indexing will just work on the external drive. Thanks again! 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions