garrytan/gstack · error · Error

refusing drift re-register of

Error message

refusing drift re-register of ${id}: autopilot active (${ap.signal}). Stop autopilot, then re-run /sync-gbrain.

What it means

Thrown by ensureSourceRegistered() in the drift branch when a source is registered at a different path than requested AND detectAutopilot reports autopilot active. Re-registering a drifted source requires a destructive remove (which deletes pages/chunks/embeddings) followed by re-add; running that while autopilot is indexing would corrupt the in-progress rebuild. The refusal is fatal by design — returning changed=false would silently hide the drift. The message names the id and the autopilot signal so the operator knows what to stop.

Solutions

  1. Stop the autopilot process (the named signal tells you which), then re-run /sync-gbrain.
  2. If drift is perpetual (every sync reports it), align the registered path with the actual path permanently — re-add at the canonical location once.
  3. Verify detectAutopilot isn't reporting a stale signal (e.g. a leftover lockfile); clean it if so.
  4. As a last resort, manually remove the drifted source via gbrain CLI after confirming autopilot is stopped.
Defensive patterns

Strategy: validation

Validate before calling

import { detectAutopilot } from './gbrain-guards';
function assertAutopilotIdle(env: NodeJS.ProcessEnv = process.env): void {
  const ap = detectAutopilot(env, {});
  if (ap.active) throw new Error(`Stop autopilot (${ap.signal}) before re-registering sources.`);
}

Try / catch

try {
  return await ensureSourceRegistered(id, path, options);
} catch (e) {
  if (e instanceof Error && e.message.startsWith('refusing drift re-register') && /autopilot active/.test(e.message)) {
    // Stop autopilot, then re-run /sync-gbrain
    throw new Error('Autopilot is running. Stop it, then re-run /sync-gbrain.');
  }
  throw e;
}

Prevention

When it happens

Trigger: probeSource returns status=match but the realpath-normalized path differs from the requested path (drift), and detectAutopilot(env).active is true. The function then throws before attempting the remove+add, instructing the operator to stop autopilot first.

Common situations: Repository moved on disk so the source path changed while gbrain still tracks the old location, and autopilot is mid-reindex; a CI machine clones to a temp dir each run causing perpetual drift; symlink re-resolved to a different absolute path; autopilot left running from a previous session.

Related errors


AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12). Data as JSON: /api/errors/39e5bb85819d33be. Report an issue: GitHub.

Appendix: source

Thrown at lib/gbrain-sources.ts:217

    // as "source registration failed" and aborts the whole /sync-gbrain code
    // stage for any source that has drifted to a new path. This matches the
    // flag the orchestrator's own safeSourcesRemove() already passes.
    if (state.status === "drift") {
      // Loud drift observability: if this line shows up on every sync for some
      // environment, drift is perpetual there and the reindex-in-place design
      // from #1985 should be promoted (drop+rebuild re-embeds the full index).
      console.error(
        `[gbrain-sources] drift: ${id} registered at ${state.registered_path} -> re-registering at ${path}`,
      );

      // #1734: this remove deletes the source's pages/chunks/embeddings, so it
      // runs only behind the same data-loss guards as the orchestrator's
      // safeSourcesRemove(). A refusal is FATAL here (not best-effort): without
      // the remove the add cannot proceed, and returning changed=false would
      // silently hide the drifted registration.
      const ap = detectAutopilot(env ?? process.env, options.autopilotProbe ?? {});
      if (ap.active) {
        throw new Error(
          `refusing drift re-register of ${id}: autopilot active (${ap.signal}). ` +
            `Stop autopilot, then re-run /sync-gbrain.`,
        );
      }
      const decision = decideSourceRemove(id, env ?? process.env, options.removeDecision ?? {});
      if (!decision.allow) {
        throw new Error(`refusing drift re-register of ${id}: ${decision.reason}`);
      }

      const rm = spawnSync(
        "gbrain",
        ["sources", "remove", id, "--yes", "--confirm-destructive", ...decision.extraArgs],
        {
          encoding: "utf-8",
          timeout: 30_000,
          env,
          shell: NEEDS_SHELL_ON_WINDOWS, // #1731: gbrain is a .cmd shim on Windows
        },

View on GitHub (pinned to 94993f7401)