chenhg5/cc-connect · error

cannot create config directory: %w

Error message

cannot create config directory: %w

What it means

AcquireInstanceLock creates the config directory (os.MkdirAll) before opening its lock file. If the directory cannot be created, the function fails with "cannot create config directory: %w". This is an OS-level failure (permissions, read-only filesystem, path is a file) surfaced before locking occurs.

Source

Thrown at cmd/cc-connect/instance_lock.go:37

// AcquireInstanceLock attempts to acquire an exclusive lock for the given config path.
// If another instance is already running with the same config, it returns an error
// containing the PID of the existing instance.
//
// The lock file is placed in the same directory as the config file, with a name
// derived from the config path hash. This allows different configs to run simultaneously.
func AcquireInstanceLock(configPath string) (*InstanceLock, error) {
	// Create lock file path based on config path
	configDir := filepath.Dir(configPath)
	configBase := filepath.Base(configPath)

	// Use a predictable name based on config filename
	lockName := fmt.Sprintf(".%s.lock", configBase)
	lockPath := filepath.Join(configDir, lockName)

	// Ensure directory exists
	if err := os.MkdirAll(configDir, 0755); err != nil {
		return nil, fmt.Errorf("cannot create config directory: %w", err)
	}

	// Open/create the lock file
	f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0644)
	if err != nil {
		return nil, fmt.Errorf("cannot open lock file: %w", err)
	}

	// Try to acquire exclusive lock (non-blocking)
	err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
	if err != nil {
		// Lock is held by another process
		f.Close()

		// Try to read PID from lock file for better error message
		pid := readPIDFromLockFile(lockPath)
		if pid > 0 {
			return nil, fmt.Errorf("another cc-connect instance is already running (PID %d) with config %s", pid, configPath)

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Check permissions on the config directory's parent and create it manually: mkdir -p $(dirname <configPath>).
  2. Verify no regular file exists at the config directory path; remove or rename it.
  3. Check disk space (df -h) and mount status (mount | grep ro) for the filesystem holding the config.
  4. Pass a --config path in a writable location (e.g. ~/.config/cc-connect/config.toml).

Example fix

// before
f, err := lock(configPath) // fails when ~/.config/cc-connect is unwritable
// after
// pre-create with correct ownership, then run
// mkdir -p ~/.config/cc-connect && chown $USER ~/.config/cc-connect
Defensive patterns

Strategy: validation

Validate before calling

dir := filepath.Dir(configPath)
if fi, err := os.Stat(dir); err == nil && !fi.IsDir() {
    return fmt.Errorf("%s is a file, not a directory", dir)
}
if err := os.MkdirAll(dir, 0755); err != nil {
    return fmt.Errorf("config dir not creatable: %w", err)
}

Try / catch

lock, err := AcquireInstanceLock(configPath)
if err != nil && strings.Contains(err.Error(), "cannot create config directory") {
    slog.Error("check permissions/ownership of config dir", "dir", filepath.Dir(configPath), "err", err)
    os.Exit(1)
}

Prevention

When it happens

Trigger: Calling AcquireInstanceLock(configPath) where filepath.Dir(configPath) does not exist and cannot be created: parent dir not writable, a non-directory file occupies the path, or the filesystem is read-only/full.

Common situations: Running cc-connect with a config path in a directory the user cannot write to (e.g. /etc or another user's home), a stale file named like the config dir, disk full, or read-only mount after system recovery.

Understand the failure class

Background: mkdir permission denied (EACCES): failed to create directory errors explained — this error's family across 32 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/de6c73d73abeb0a6. Report an issue: GitHub.