{"record":{"id":"5ff55e90ffc50324","repo":"langgenius/dify","slug":"concurrent-access-detected","errorCode":null,"errorMessage":"concurrent access detected","messagePattern":"concurrent access detected","errorType":"exception","errorClass":"ConcurrentAccessError","httpStatus":null,"severity":"warning","filePath":"cli/src/store/store.ts","lineNumber":117,"sourceCode":"          /* tmp may not exist */\n        }\n        throw err\n      }\n    }\n\n    this.dirty = false\n  }\n\n  async lock(): Promise<void> {\n    await this.ensureDir()\n    try {\n      await lockAsync(`${this.filePath}.lock`, {\n        stale: LOCK_STALE_MS,\n      })\n    } catch (err) {\n      const code = (err as NodeJS.ErrnoException).code\n      if (code === 'EEXIST') {\n        throw new ConcurrentAccessError(this.filePath)\n      }\n      throw err\n    }\n  }\n\n  async load(): Promise<void> {\n    try {\n      this.rawContent = await fsp.readFile(this.filePath, 'utf8')\n      this.dirty = false\n    } catch (err) {\n      const code = (err as NodeJS.ErrnoException).code\n      if (code !== 'ENOENT') {\n        throw err\n      }\n    }\n  }\n\n  public setRawContent(content: string): void {","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/langgenius/dify/blob/ef8544b173fd6cd7a8e71df2cab576e52bebbfbc/cli/src/store/store.ts#L99-L135","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Confirm no difyctl is actually running: `ps aux | grep difyctl`. If none, the lock is stale.","Remove the lockfile named in the error: `rm <filePath>.lock` (the hint says exactly this).","For shared CI HOME: give each job an isolated HOME or XDG_CONFIG_HOME so they don't share the lock.","Avoid running concurrent mutating difyctl commands against the same profile."],"exampleFix":"# before — second command fails\n(difyctl auth login &) ; difyctl use workspace ws_x   # races on hosts.yml\n\n# after — serialize, or isolate\n# option 1: wait for the first to finish, then run the second\n# option 2: separate HOMEs\nHOME=/tmp/dify-a difyctl auth login\nHOME=/tmp/dify-b difyctl use workspace ws_x\n# option 3: clear a confirmed-stale lock\nrm ~/.config/difyctl/hosts.yml.lock","handlingStrategy":"try-catch","validationCode":"// before mutating, ensure no other difyctl holds the lock by checking for the lockfile\nimport { stat } from 'node:fs/promises'\n\nasync function isLockStale(lockPath: string, staleMs = 30_000): Promise<boolean> {\n  try {\n    const s = await stat(lockPath)\n    return Date.now() - s.mtimeMs > staleMs\n  } catch {\n    return false // no lockfile → not stale\n  }\n}","typeGuard":null,"tryCatchPattern":"import { ConcurrentAccessError } from '@/store/errors'\n\ntry {\n  await reg.save()\n} catch (err) {\n  if (err instanceof ConcurrentAccessError) {\n    // the error message names the .lock file to remove\n    console.error(err.message)\n    // optionally: surface to the user and retry once after they confirm\n    throw err\n  }\n  throw err\n}","preventionTips":["Serialize difyctl invocations that share the same HOME/config dir.","Give parallel CI jobs isolated HOME or XDG_CONFIG_HOME so they don't share locks.","Don't SIGKILL difyctl — let it exit cleanly so it releases the lock.","If a lock is confirmed stale (older than 30s, no difyctl running), remove the named .lock file."],"tags":["store","locking","concurrency","filesystem","yaml"],"backgroundTag":null,"analyzedSha":"ef8544b173fd6cd7a8e71df2cab576e52bebbfbc","analyzedAt":"2026-08-12T05:15:17.394Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:31:55.035Z"}