langgenius/dify · warning · ConcurrentAccessError

concurrent access detected

Error message

concurrent access detected

What it means

ConcurrentAccessError (a BaseError, ClientError → exit 1) from FileBasedStore.lock (store.ts:117) when the lockfile call rejects with errno EEXIST — meaning another process holds `${filePath}.lock`. The lock has a 30s stale threshold (LOCK_STALE_MS), so a crashed process's lock auto-expires after 30s. The error message includes the file path and instructs removing the .lock file. Operations go through withLock (get/set/unset) and getTyped/setTyped, so any store read or write can hit this.

Source

Thrown at cli/src/store/store.ts:117

          /* tmp may not exist */
        }
        throw err
      }
    }

    this.dirty = false
  }

  async lock(): Promise<void> {
    await this.ensureDir()
    try {
      await lockAsync(`${this.filePath}.lock`, {
        stale: LOCK_STALE_MS,
      })
    } catch (err) {
      const code = (err as NodeJS.ErrnoException).code
      if (code === 'EEXIST') {
        throw new ConcurrentAccessError(this.filePath)
      }
      throw err
    }
  }

  async load(): Promise<void> {
    try {
      this.rawContent = await fsp.readFile(this.filePath, 'utf8')
      this.dirty = false
    } catch (err) {
      const code = (err as NodeJS.ErrnoException).code
      if (code !== 'ENOENT') {
        throw err
      }
    }
  }

  public setRawContent(content: string): void {

View on GitHub (pinned to ef8544b173)

Solutions

  1. Wait briefly and retry — if the other process is healthy it will release the lock; if it crashed, the 30s stale timer will reap it.
  2. Confirm no difyctl is actually running: `ps aux | grep difyctl`. If none, the lock is stale.
  3. Remove the lockfile named in the error: `rm <filePath>.lock` (the hint says exactly this).
  4. For shared CI HOME: give each job an isolated HOME or XDG_CONFIG_HOME so they don't share the lock.
  5. Avoid running concurrent mutating difyctl commands against the same profile.

Example fix

# before — second command fails
(difyctl auth login &) ; difyctl use workspace ws_x   # races on hosts.yml

# after — serialize, or isolate
# option 1: wait for the first to finish, then run the second
# option 2: separate HOMEs
HOME=/tmp/dify-a difyctl auth login
HOME=/tmp/dify-b difyctl use workspace ws_x
# option 3: clear a confirmed-stale lock
rm ~/.config/difyctl/hosts.yml.lock
Defensive patterns

Strategy: try-catch

Validate before calling

// before mutating, ensure no other difyctl holds the lock by checking for the lockfile
import { stat } from 'node:fs/promises'

async function isLockStale(lockPath: string, staleMs = 30_000): Promise<boolean> {
  try {
    const s = await stat(lockPath)
    return Date.now() - s.mtimeMs > staleMs
  } catch {
    return false // no lockfile → not stale
  }
}

Try / catch

import { ConcurrentAccessError } from '@/store/errors'

try {
  await reg.save()
} catch (err) {
  if (err instanceof ConcurrentAccessError) {
    // the error message names the .lock file to remove
    console.error(err.message)
    // optionally: surface to the user and retry once after they confirm
    throw err
  }
  throw err
}

Prevention

When it happens

Trigger: Two difyctl invocations racing on the same hosts.yml (or other store file): a background script and an interactive session, or two CI jobs sharing a HOME. Also a previous difyctl killed with SIGKILL leaving a lock younger than 30s. The lock is per-file (`${filePath}.lock`), so concurrent commands touching different files don't interfere.

Common situations: CI runner that reuses HOME across parallel jobs; a long-running watch loop (`difyctl use` in one pane, `difyctl auth login` in another); a hung process that didn't exit; NFS/home with flaky locking; the lockfile itself left behind by a kernel OOM kill.

Related errors


AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12). Data as JSON: /api/errors/5ff55e90ffc50324. Report an issue: GitHub.