larksuite/cli · warning · ErrHeld
%w (lock: %s, syscall: %v)
Error message
%w (lock: %s, syscall: %v)
What it means
This error signals that an exclusive, non-blocking flock on a lock file failed because another process already holds the lock. It wraps the sentinel ErrHeld with %w so callers can use errors.Is(err, lockfile.ErrHeld) to distinguish 'lock is busy' from other I/O failures; the lock file path and the raw syscall error are embedded for context. It is the Unix implementation (syscall.Flock LOCK_EX|LOCK_NB) of busy-lock detection.
Source
Thrown at internal/lockfile/lock_unix.go:17
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
//go:build !windows
package lockfile
import (
"fmt"
"os"
"syscall"
)
func tryLockFile(f *os.File) error {
err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
if err != nil {
return fmt.Errorf("%w (lock: %s, syscall: %v)", ErrHeld, f.Name(), err)
}
return nil
}
func unlockFile(f *os.File) error {
return syscall.Flock(int(f.Fd()), syscall.LOCK_UN)
}
View on GitHub (pinned to 7fd6ef3c07)
Solutions
- Check errors.Is(err, lockfile.ErrHeld) in the caller; if held, wait and retry with backoff rather than treating it as fatal corruption.
- Find the competing process holding the lock (lsof on the lock file path from the message) and wait for it to finish.
- Do not delete the lock file while another process may hold it — flock releases automatically when the holder exits.
- On NFS, move the lock file to a local filesystem where flock is reliable.
Example fix
// before
cmd1: lark-cli config set a=b &
cmd2: lark-cli config set c=d // fails: lock held
// after: serialize or retry
if err := store.Update(...); errors.Is(err, lockfile.ErrHeld) {
time.Sleep(100 * time.Millisecond)
return store.Update(...) // retry
} Defensive patterns
Strategy: retry
Type guard
func IsLockHeld(err error) bool {
return errors.Is(err, lockfile.ErrHeld)
} Try / catch
err := lf.TryLock()
if IsLockHeld(err) {
// busy, not corrupt: back off and retry
time.Sleep(200 * time.Millisecond)
err = lf.TryLock()
}
if err != nil {
return err
}
defer lf.Unlock() Prevention
- Check errors.Is(err, ErrHeld) instead of parsing message text.
- Serialize CLI state-mutating commands in scripts; avoid parallel invocations.
- Never delete lock files while a holder may exist — flock auto-releases on exit.
- Keep lock files on local filesystems, not NFS.
When it happens
Trigger: tryLockFile calls syscall.Flock(fd, LOCK_EX|LOCK_NB) on the lock file and gets EWOULDBLOCK/EAGAIN (held by another process) or another flock error — the wrapper unfortunately labels both, but ErrHeld semantics target the busy case.
Common situations: Two CLI invocations racing on the same config/profile lock (e.g. concurrent `lark-cli auth login` in two terminals); a stale lock held by a crashed process whose fd lingers (rare for flock — dies with the process); NFS mounts where flock semantics are unreliable; a long-running command (update/install) still holding the lock.
Related errors
- cannot restore blocking mode: %w
- cannot stat opened file: %w
- file changed between validation and open
- not a regular file (directories, devices, FIFOs, and sockets
- file has multiple hard links, so the other names it can be r
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/7b727e7cf3abfb27.
Report an issue: GitHub.