Repository navigation
GBRAIN_HOME resolved differently by gstack and gbrain (missing .gbrain segment) #2521
Description
Summary
gstack and gbrain resolve GBRAIN_HOME to different config paths. gstack treats it as the config directory; gbrain treats it as the parent and appends .gbrain. With GBRAIN_HOME set, gstack classifies engine status from a file gbrain never reads.
The divergence
gstack — lib/gbrain-local-status.ts:127:
const gbrainHome = e.GBRAIN_HOME || join(userHome(e), ".gbrain");
// → $GBRAIN_HOME/config.jsonSame convention in bin/gstack-gbrain-detect:
const GBRAIN_CONFIG = join(
process.env.GBRAIN_HOME || join(userHome(), ".gbrain"),
"config.json",
);gbrain — src/core/config.ts, configDir():
// GBRAIN_HOME is a parent dir; we always append '.gbrain' ourselves so
// setting GBRAIN_HOME=/tmp/x yields configDir() === '/tmp/x/.gbrain'.
return join(trimmed, '.gbrain');
// → $GBRAIN_HOME/.gbrain/config.jsonThe comment is explicit, so this is gbrain's intended contract; gstack is the one that deviates.
GBRAIN_HOME |
gstack reads | gbrain reads |
|---|---|---|
| unset | ~/.gbrain/config.json |
~/.gbrain/config.json ✅ |
/tmp/x |
/tmp/x/config.json |
/tmp/x/.gbrain/config.json ❌ |
Why it matters
localEngineStatus() reads the config and spawns gbrain sources list as the probe. With GBRAIN_HOME set, the two halves look at different installs: the file checks (missing-config, the remote_mcp thin-client pre-probe, configuredEngine() for the engine-locked branch) read gstack's path, while the spawned CLI reads gbrain's. Any resulting status is arbitrary.
Concretely, the missing-config / broken-config branches can fire against a perfectly healthy install, or — as I hit — a remote_mcp marker placed at gstack's path yields thin-client from gstack-gbrain-detect while gbrain itself reports No brain configured. Run: gbrain init.
The comment at bin/gstack-gbrain-detect:51-53 says this must "stay consistent with lib/gbrain-local-status's config resolution" — the two gstack sites are consistent with each other, just not with gbrain.
Reproduce
mkdir -p /tmp/x && cp ~/.gbrain/config.json /tmp/x/config.json
GBRAIN_HOME=/tmp/x GSTACK_DETECT_NO_CACHE=1 gstack-gbrain-detect
# → reads /tmp/x/config.json, reports a status derived from it
GBRAIN_HOME=/tmp/x gbrain sources list --json
# → "No brain configured. Run: gbrain init" (looked in /tmp/x/.gbrain/)Suggested fix
Append .gbrain to the override in both gstack sites, matching configDir():
const gbrainHome = e.GBRAIN_HOME ? join(e.GBRAIN_HOME, ".gbrain") : join(userHome(e), ".gbrain");Worth checking buildGbrainEnv in lib/gbrain-exec.ts for the same assumption, and the gbrain_home cache-invariant key at lib/gbrain-local-status.ts:82 / :212 (it stores the raw env value, which stays correct either way, but the cached status it guards would have been computed from the wrong file).
Low blast radius today — it only bites users who set GBRAIN_HOME (tests, Docker, multi-tenant, which is exactly what gbrain documents the override for).
Environment
gstack 1.61.0.0, gbrain 0.42.63.0, macOS 26.x
Activity
v1.67.0.0 fix: the tracker wave — XProtect self-heal, complete instal…