larksuite/cli · error · ErrHeld
%w: %s
Error message
%w: %s
What it means
TryLock refuses to re-lock a LockFile instance that already holds a lock, wrapping the ErrHeld sentinel with the lock path. ErrHeld is the package's retryable-contention marker; callers use errors.Is to distinguish contention (lock held) from real failures.
Source
Thrown at internal/lockfile/lockfile.go:50
// ForSubscribe sanitises appID against path traversal before forming the lock filename.
func ForSubscribe(appID string) (*LockFile, error) {
if appID == "" {
return nil, fmt.Errorf("app ID must not be empty")
}
dir := filepath.Join(core.GetConfigDir(), "locks")
if err := vfs.MkdirAll(dir, 0700); err != nil {
return nil, fmt.Errorf("create lock dir: %w", err)
}
safe := safeIDChars.ReplaceAllString(appID, "_")
name := filepath.Base(fmt.Sprintf("subscribe_%s.lock", safe))
path := filepath.Join(dir, name)
return New(path), nil
}
// TryLock acquires an exclusive non-blocking lock; auto-released on process exit.
func (l *LockFile) TryLock() error {
if l.file != nil {
return fmt.Errorf("%w: %s", ErrHeld, l.path)
}
f, err := vfs.OpenFile(l.path, os.O_CREATE|os.O_RDWR, 0600)
if err != nil {
return fmt.Errorf("open lock file: %w", err)
}
if err := tryLockFile(f); err != nil {
f.Close()
return err
}
l.file = f
return nil
}
// Unlock keeps the file on disk to avoid inode-reuse races between unlock and competing open+flock.
func (l *LockFile) Unlock() error {
if l.file == nil {
return nil
}View on GitHub (pinned to 7fd6ef3c07)
Solutions
- Call Unlock() before attempting TryLock again on the same instance.
- Create a fresh LockFile via lockfile.New/ForSubscribe for each acquisition scope.
- Structure code as acquire/deferred-Unlock so re-entry cannot happen.
- If you need contention detection against other processes, note TryLock already returns ErrHeld from tryLockFile; branch on errors.Is(err, ErrHeld).
Example fix
// before
if err := lf.TryLock(); err != nil { ... }
if err := lf.TryLock(); err != nil { ... } // second call errors
// after
if err := lf.TryLock(); err != nil { ... }
lf.Unlock()
if err := lf.TryLock(); err != nil { ... } Defensive patterns
Strategy: try-catch
Try / catch
if err := lf.TryLock(); err != nil {
if errors.Is(err, lockfile.ErrHeld) {
lf.Unlock() // or skip: already locked by this instance
}
} Prevention
- Pair every TryLock with a deferred Unlock
- Never reuse a LockFile across acquisition scopes; create a new one
- Wrap lock/unlock in acquire/release helpers to prevent double TryLock
When it happens
Trigger: Calling TryLock() twice on the same *LockFile without an intervening Unlock(), or reusing a LockFile struct whose lock was acquired in a prior iteration.
Common situations: Retry loops that forget to Unlock before the next TryLock; shared LockFile handed to two code paths; forgot release after a long-lived subscribe loop restart.
Related errors
- %w (lock: %s, syscall: %v)
- Invalid column: {column!r}
- Invalid column index: {index}
- anchor outside sheet: {position!r}
- invalid chart size: {size!r}
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/d82d7ec4326e97dc.
Report an issue: GitHub.