chenhg5/cc-connect · error

cannot open lock file: %w

Error message

cannot open lock file: %w

What it means

After ensuring the config directory exists, AcquireInstanceLock opens the lock file .<config>.lock with os.OpenFile(O_CREATE|O_RDWR). Any open failure is wrapped as "cannot open lock file: %w". This is an OS-level open error, distinct from the lock-contention error paths.

Source

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

// 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)
		}
		return nil, fmt.Errorf("another cc-connect instance is already running with config %s", configPath)
	}

	// Write our PID to the lock file for diagnostics
	pid := os.Getpid()

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Inspect and fix permissions on the config directory (chmod u+w / chown $USER).
  2. If .<config>.lock exists as a directory, remove it: rm -rf <configDir>/.<configBase>.lock.
  3. Check disk space and read-only mount status for the filesystem.
  4. Run cc-connect as the same user that owns the config directory (avoid mixing sudo runs).
Defensive patterns

Strategy: validation

Validate before calling

lockPath := filepath.Join(filepath.Dir(configPath), "."+filepath.Base(configPath)+".lock")
if fi, err := os.Stat(lockPath); err == nil && fi.IsDir() {
    return fmt.Errorf("%s is a directory; remove it", lockPath)
}
test, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
    return err
}
test.Close()

Try / catch

lock, err := AcquireInstanceLock(configPath)
if err != nil && strings.Contains(err.Error(), "cannot open lock file") {
    slog.Error("lock file unwritable — check dir perms/stale directory", "err", err)
    os.Exit(1)
}

Prevention

When it happens

Trigger: os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0644) fails: the config directory is not writable, the lock path exists as a directory, or the filesystem is read-only/full.

Common situations: A previous crash left a directory named .config.toml.lock in the config dir; the config dir is owned by root after running once with sudo; read-only /etc or NFS mount.

Understand the failure class

Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.

Related errors


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