Cherry-picked from #1508 by jasshultz, restructured per codex review #4
and #7 to widen scope and centralize the spawn surface.
The bug: gbrain auto-loads .env.local from cwd via dotenv. When
/sync-gbrain runs inside a Next.js / Prisma / Rails project whose
.env.local defines its own DATABASE_URL (pointing at the app's local
DB), gbrain reads that value instead of its own
~/.gbrain/config.json — auth fails, code + memory stages crash.
This commit:
- Adds lib/gbrain-exec.ts: buildGbrainEnv, spawnGbrain, execGbrainJson,
execGbrainText, spawnGbrainAsync (the last one for memory-ingest's
streaming gbrain import call). buildGbrainEnv seeds DATABASE_URL from
${GBRAIN_HOME:-$HOME/.gbrain}/config.json, returns a fresh env object
(never the caller's by identity — codex review #11), and honors the
GSTACK_RESPECT_ENV_DATABASE_URL=1 escape hatch.
- Routes every gbrain spawn in bin/gstack-gbrain-sync.ts and
bin/gstack-memory-ingest.ts through the helpers. Both files now own
zero direct spawnSync("gbrain"|spawn("gbrain"|execFileSync("gbrain"
call sites.
- Threads buildGbrainEnv into the spawnSync("bun", [memory-ingest], ...)
grandchild in runMemoryIngest (codex review #7). Without this, the
parent fix is half-baked — the bun child inherits a clean env but
needs DATABASE_URL pre-seeded too. spawnGbrainAsync inside
memory-ingest provides defense in depth for standalone invocations.
- Adds GBRAIN_HOME support — aligns with detectEngineTier (already
honors GBRAIN_HOME) so all gstack-side gbrain calls agree on which
config file matters. Resolves baseEnv.HOME first, then homedir(), so
test injection works without process-wide HOME mutation.
- Adds test/build-gbrain-env.test.ts: 10 unit tests covering all five
env-seeding branches (seed from config / override caller /
GSTACK_RESPECT escape hatch / missing config / unparseable config /
no database_url field / GBRAIN_HOME path / object-identity guard /
unrelated-vars preservation / idempotent-when-matches).
- Adds test/gbrain-exec-invariant.test.ts: static-source check that
greps both bin/gstack-gbrain-sync.ts and bin/gstack-memory-ingest.ts
for direct spawnSync("gbrain"|spawn("gbrain"|execFileSync("gbrain"|
execSync(...gbrain matches and fails the build if any are found.
Refactor-proof against future contributors adding a new gbrain spawn
without env threading.
The invariant is intentionally narrow — only the two files where the
DATABASE_URL bug actually hurts users are guarded. Migrating the
spawn sites in lib/gbrain-local-status.ts, lib/gstack-memory-helpers.ts,
and bin/gstack-brain-context-load.ts is a follow-up.
Co-Authored-By: Jason Shultz <jasshultz@gmail.com>
Co-Authored-By: Claude <noreply@anthropic.com>