abhigyanpatwari/GitNexus · error · GroupSyncLockError

timeout

timeout

Error message

Could not acquire the sync lock for group "${path.basename(groupDir)}" (${getGroupSyncLockDir(groupDir)}). ${err.message} Nothing was written and this group was not synced.

What it means

withGroupSyncLock failed to acquire the per-group sync lock (`<groupDir>/sync-lock`) because the underlying index-lock wait timed out with a guard timeout (isIndexLockGuardTimeout). Only a group sync ever holds this lock, so the error is worded around what is known: which group, which lock path, and the underlying wait error. Nothing was written and the group was not synced.

Solutions

  1. Wait for the other sync of this group to finish, then re-run the sync.
  2. Check for stale locks in the group's sync-lock directory (getGroupSyncLockDir) and remove them only if no sync process is running.
  3. Ensure only one GitNexus process (MCP server, serve, CLI) syncs the same group at a time.

Example fix

// before: two concurrent syncs of the same group
await Promise.all([syncGroup(g), syncGroup(g)]);
// after
await syncGroup(g); // serialize; or use a shared queue per groupDir
Defensive patterns

Strategy: retry

Validate before calling

import fs from 'node:fs';
// Check no sync lock exists before starting
if (fs.existsSync(groupSyncLockPath)) {
  console.warn('Another sync of this group may be running; wait before retrying.');
}

Type guard

const isGroupSyncLockError = (e: unknown): e is GroupSyncLockError =>
  e instanceof GroupSyncLockError && e.code === 'timeout';

Try / catch

try {
  await withGroupSyncLock(groupDir, () => doSync());
} catch (err) {
  if (isGroupSyncLockError(err)) {
    console.error(`Group ${err.groupDir} is being synced elsewhere; nothing was written.`);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling a group sync API wrapped in withGroupSyncLock while another sync of the same group is in progress (or a stale lock from a crashed sync exists), and the IndexLockTimeoutError is classified as a guard timeout.

Common situations: Two concurrent `gitnexus` syncs targeting the same group (e.g. MCP server and CLI both syncing); a previous sync crashed leaving a lock file behind; slow disk making the guard wait exceed its budget.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@ac9a4e9abd (2026-09-15). Data as JSON: /api/errors/812104f72d011ba8. Report an issue: GitHub.

Appendix: source

Thrown at gitnexus/src/core/group/group-lock.ts:154

      timeoutMs: GROUP_SYNC_LOCK_TIMEOUT_MS,
      // `acquireIndexLock`'s own `log` texts name an "analyze" holder, which
      // misattributes a group-sync wait — the same reason `withRegistryLock`
      // supplies its own line instead of passing `log` through.
      onWaitStart: () =>
        logger.info(
          { groupDir },
          'Waiting for another GitNexus process to finish syncing this group…',
        ),
    });
  } catch (err) {
    // The inherited message names "another gitnexus analyze" as the holder —
    // a cause this detection path cannot establish. Nothing but a group sync
    // ever locks `<groupDir>/sync-lock` (see the module header), and on the
    // socket backend the holder is not identifiable at all. Re-word it around
    // what IS known: which group, which operation, and how long we waited.
    if (err instanceof IndexLockTimeoutError) {
      if (isIndexLockGuardTimeout(err)) {
        throw new GroupSyncLockError(
          'timeout',
          groupDir,
          `Could not acquire the sync lock for group "${path.basename(groupDir)}" ` +
            `(${getGroupSyncLockDir(groupDir)}). ${err.message} ` +
            `Nothing was written and this group was not synced.`,
          err,
        );
      }
      throw new GroupSyncLockError(
        'timeout',
        groupDir,
        `Timed out after ${Date.now() - acquireStartedAt}ms waiting for the sync lock on ` +
          `group "${path.basename(groupDir)}" (${getGroupSyncLockDir(groupDir)}). ` +
          // `holderKnown` is false on the socket backend and on the file
          // backend's malformed/vanished-lock timeouts, where `holder` is a
          // placeholder (`pid -1`). Presenting that as a real owner would be the
          // same unestablished claim in a new form.
          (err.holderKnown

View on GitHub (pinned to ac9a4e9abd)