Skip to content

autopilot lock file ignores GBRAIN_HOME — second brain silently respawns forever #1226

@rafaelreis-r

Description

@rafaelreis-r

Summary

gbrain autopilot uses a lock file at $HOME/.gbrain/autopilot.lock regardless of GBRAIN_HOME. When two brains run on the same host (each with its own GBRAIN_HOME and database), they compete for the same lock — the second autopilot silently exits with code 0 every time, producing a silent respawn loop under launchd/systemd.

Affected

  • v0.35.6 (confirmed; introduced when multi-brain support landed)
  • Any setup with multiple brains sharing a host

Repro

  1. Two brains: ~/.gbrain/ and ~/.gbrain-side/, each with its own config.json and Postgres DB.
  2. Two launchd KeepAlive agents, each setting its own GBRAIN_HOME env:
    • com.gbrain.autopilotGBRAIN_HOME=~/.gbrain
    • com.gbrain-side.autopilotGBRAIN_HOME=~/.gbrain-side
  3. Start both.
  4. Observe: only the first autopilot to take the lock does any work. The second exits with "Another autopilot instance is running (lock file is fresh). Exiting." and launchd respawns it every ThrottleInterval seconds.

In our setup this produced 46,388 silent failures in ~3 days before we traced it.

Root cause

src/commands/autopilot.ts:

const lockPath = join(process.env.HOME || '', '.gbrain', 'autopilot.lock');

The path is hardcoded to $HOME/.gbrain/, ignoring GBRAIN_HOME. Every other piece of state (config, sync_repo_path, DB) is correctly scoped by GBRAIN_HOME; the lock is the one place it isn't.

Why it stays invisible

  • Exit code is 0 (not a crash) → launchd considers it a normal restart.
  • Log line is informational, not an error.
  • launchctl list shows the agent as healthy because it's always "currently running" between respawns.
  • Process never gets to gbrain dream / extract / embed / orphans → the brain stops evolving but nothing alerts.

Fix

Scope the lock path to GBRAIN_HOME when set. PR: #

const gbrainHome = process.env.GBRAIN_HOME || join(process.env.HOME || '', '.gbrain');
const lockPath = join(gbrainHome, 'autopilot.lock');

Each brain gets its own lock; they coexist cleanly. Single-brain installs are unaffected (fallback to $HOME/.gbrain).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions