Skip to content

GBRAIN_HOME resolved differently by gstack and gbrain (missing .gbrain segment) #2521

Closed

Description

@d-danielsun

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.json

Same 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.json

The 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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