mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-10 15:09:00 +02:00
fix: resolve GBRAIN_HOME with gbrain's parent-dir semantics (#2521)
gstack treated GBRAIN_HOME as the config directory; gbrain's configDir() treats it as the PARENT and always appends `.gbrain` itself (the contract is explicit in gbrain's source: GBRAIN_HOME=/tmp/x → /tmp/x/.gbrain/ config.json). With GBRAIN_HOME set, gstack classified engine status from a file gbrain never reads — the probe's two halves (file checks vs the spawned `gbrain sources list`) looked at DIFFERENT installs, so any resulting status was arbitrary: missing-config/broken-config against healthy installs, or a thin-client marker gstack saw that gbrain itself reported as "No brain configured". New shared resolver `gbrainConfigDir()` in lib/gbrain-exec.ts is the single source of truth. All seven gstack sites route through the contract: - lib/gbrain-local-status.ts gbrainConfigPath (the classifier's file half) - bin/gstack-gbrain-detect GBRAIN_CONFIG + readRemoteMcpUrl - lib/gbrain-exec.ts buildGbrainEnv (the probe's DATABASE_URL seed — fixing only the classifier would have left the split-brain in the spawn half, flagged by the reporter) - lib/gbrain-guards.ts gbrainHome (clones-dir + autopilot-lock paths) - lib/gstack-memory-helpers.ts gbrainConfigPath (engine-tier fallback) - bin/gstack-gbrain-install pre-doctor config check (shell) Unit tests cover GBRAIN_HOME set (config found at $GBRAIN_HOME/.gbrain), the old flat layout explicitly NOT read (both classifier and buildGbrainEnv), and unset (~/.gbrain unchanged). Existing fixtures that encoded the deviant flat layout are updated to gbrain's contract. Root-cause analysis by @d-danielsun in #2521. Deviation from the 3-site plan spec: the same deviant resolution existed in four more sites (buildGbrainEnv, gbrain-guards, memory-helpers, gbrain-install); fixing only three would have left gstack disagreeing with itself as well as with gbrain, so the whole class moved to the shared resolver in one change. Fixes #2521 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
ce4a7bbb7e
commit
5854d122d3
+22
-9
@@ -21,10 +21,12 @@
|
||||
* spawn. This is the central bug the helper exists to prevent
|
||||
* regressing on.
|
||||
*
|
||||
* 3. **`GBRAIN_HOME` honored consistently.** Other gstack helpers
|
||||
* (`detectEngineTier`) already honor `GBRAIN_HOME`. `buildGbrainEnv`
|
||||
* reads from `${GBRAIN_HOME:-$HOME/.gbrain}/config.json` so all
|
||||
* gstack-side gbrain calls agree on which config file matters.
|
||||
* 3. **`GBRAIN_HOME` honored consistently — with gbrain's own semantics
|
||||
* (#2521).** gbrain's configDir() treats `GBRAIN_HOME` as a PARENT
|
||||
* directory and always appends `.gbrain` itself (GBRAIN_HOME=/tmp/x
|
||||
* → /tmp/x/.gbrain/config.json). Every gstack-side read goes through
|
||||
* `gbrainConfigDir()` below so gstack and gbrain agree on which
|
||||
* config file matters.
|
||||
*
|
||||
* **Escape hatch:** `GSTACK_RESPECT_ENV_DATABASE_URL=1` returns the
|
||||
* caller's env unchanged. Use only when the brain intentionally lives in
|
||||
@@ -75,8 +77,21 @@ export function isTransactionModePooler(url: string): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an env dict with DATABASE_URL seeded from
|
||||
* `${GBRAIN_HOME:-$HOME/.gbrain}/config.json`. Returns the base env
|
||||
* gbrain's config directory, matching gbrain's own configDir() contract
|
||||
* (#2521): `GBRAIN_HOME` is a PARENT directory — gbrain always appends
|
||||
* `.gbrain` itself, so GBRAIN_HOME=/tmp/x reads /tmp/x/.gbrain/config.json.
|
||||
* Unset → ~/.gbrain. Every gstack-side gbrain-config read MUST resolve
|
||||
* through this helper, or gstack classifies engine status from a file
|
||||
* gbrain never reads.
|
||||
*/
|
||||
export function gbrainConfigDir(env: NodeJS.ProcessEnv = process.env): string {
|
||||
if (env.GBRAIN_HOME) return join(env.GBRAIN_HOME, ".gbrain");
|
||||
return join(env.HOME || homedir(), ".gbrain");
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an env dict with DATABASE_URL seeded from gbrain's config.json
|
||||
* (resolved via `gbrainConfigDir`). Returns the base env
|
||||
* unchanged when:
|
||||
* - `GSTACK_RESPECT_ENV_DATABASE_URL=1` (intentional opt-out),
|
||||
* - the config file is missing or unparseable,
|
||||
@@ -98,9 +113,7 @@ export function buildGbrainEnv(opts: BuildGbrainEnvOptions = {}): NodeJS.Process
|
||||
const out: NodeJS.ProcessEnv = { ...baseEnv };
|
||||
if (baseEnv.GSTACK_RESPECT_ENV_DATABASE_URL === "1") return out;
|
||||
|
||||
const homeBase = baseEnv.HOME || homedir();
|
||||
const gbrainHome = baseEnv.GBRAIN_HOME || join(homeBase, ".gbrain");
|
||||
const configPath = join(gbrainHome, "config.json");
|
||||
const configPath = join(gbrainConfigDir(baseEnv), "config.json");
|
||||
if (!existsSync(configPath)) return out;
|
||||
|
||||
let cfg: GbrainConfig = {};
|
||||
|
||||
@@ -36,7 +36,9 @@ import { execGbrainJson, execGbrainText, NEEDS_SHELL_ON_WINDOWS } from "./gbrain
|
||||
import { parseSourcesList, type GbrainSourceRow } from "./gbrain-sources";
|
||||
|
||||
export function gbrainHome(env: NodeJS.ProcessEnv = process.env): string {
|
||||
return env.GBRAIN_HOME || join(homedir(), ".gbrain");
|
||||
// #2521: GBRAIN_HOME is a PARENT dir per gbrain's configDir() contract —
|
||||
// gbrain appends `.gbrain` itself, so gstack must too.
|
||||
return env.GBRAIN_HOME ? join(env.GBRAIN_HOME, ".gbrain") : join(homedir(), ".gbrain");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,7 +48,7 @@ import {
|
||||
import { atomicWriteSync } from "./fs-atomic";
|
||||
import { homedir } from "os";
|
||||
import { dirname, join } from "path";
|
||||
import { buildGbrainEnv, NEEDS_SHELL_ON_WINDOWS } from "./gbrain-exec";
|
||||
import { buildGbrainEnv, gbrainConfigDir, NEEDS_SHELL_ON_WINDOWS } from "./gbrain-exec";
|
||||
|
||||
export type LocalEngineStatus =
|
||||
| "ok"
|
||||
@@ -122,11 +122,14 @@ export function cacheFilePath(): string {
|
||||
);
|
||||
}
|
||||
|
||||
/** Honors GBRAIN_HOME (codex D11) — same resolution as buildGbrainEnv. */
|
||||
/**
|
||||
* Honors GBRAIN_HOME (codex D11) with gbrain's own configDir() semantics
|
||||
* (#2521): GBRAIN_HOME is a parent dir, `.gbrain` is appended. Same
|
||||
* resolution as buildGbrainEnv — both route through gbrainConfigDir.
|
||||
*/
|
||||
function gbrainConfigPath(env?: NodeJS.ProcessEnv): string {
|
||||
const e = env ?? process.env;
|
||||
const gbrainHome = e.GBRAIN_HOME || join(userHome(e), ".gbrain");
|
||||
return join(gbrainHome, "config.json");
|
||||
return join(gbrainConfigDir(e), "config.json");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
|
||||
import { appendJsonl } from "./jsonl-store";
|
||||
import { gbrainConfigDir } from "./gbrain-exec";
|
||||
import { dirname, join } from "path";
|
||||
import { execFileSync } from "child_process";
|
||||
import { homedir } from "os";
|
||||
@@ -256,12 +257,13 @@ export function detectEngineTier(): EngineDetect {
|
||||
}
|
||||
|
||||
// Returns gbrain's config.json path, honoring GBRAIN_HOME env var with a
|
||||
// fallback to ~/.gbrain. gbrain >=0.25 dropped the top-level `engine` field
|
||||
// fallback to ~/.gbrain. Resolution matches gbrain's own configDir()
|
||||
// contract (#2521): GBRAIN_HOME is a parent dir, `.gbrain` is appended.
|
||||
// gbrain >=0.25 dropped the top-level `engine` field
|
||||
// from doctor output, so this file is the only reliable source for engine
|
||||
// detection on that version. See #1415.
|
||||
function gbrainConfigPath(): string {
|
||||
const root = process.env.GBRAIN_HOME || join(homedir(), ".gbrain");
|
||||
return join(root, "config.json");
|
||||
return join(gbrainConfigDir(process.env), "config.json");
|
||||
}
|
||||
|
||||
// Best-effort JSONL append to ~/.gstack/.gbrain-errors.jsonl. Never throws.
|
||||
|
||||
Reference in New Issue
Block a user