#2883·gstack

gstack-brain-context-load shells out to `gbrain list_pages`, which is an MCP tool name and not a CLI verb — brain context is always 0 bytes

Author: hmalinskiCreated Sep 16, 2026Updated Sep 16, 2026

Summary

bin/gstack-brain-context-load.ts builds its argv as ["list_pages", "--limit", ...] and calls spawnSync("gbrain", cliArgs). list_pages is the MCP tool name; the CLI verb is list. Every invocation exits non-zero with Unknown command: list_pages, the dispatch returns { ok: false, bytes: 0 }, and no brain context is ever loaded into any skill.

It fails silently: the caller degrades to Grep with no user-visible error.

Reproduction

$ gbrain list_pages --limit 3
Unknown command: list_pages
Run gbrain --help for available commands.

$ gbrain list --limit 3
<3 rows>

Measured end-to-end with the loader's own --explain:

$ gstack-brain-context-load --skill investigate --repo <slug> --explain
[brain-context-load] mode=manifest queries=3
  SKIP  prior-investigations   kind=list  bytes=0  (gbrain list_pages exited 1)
  SKIP  project-learnings      kind=filesystem  bytes=0  (no matches)
  SKIP  recent-eureka          kind=filesystem  bytes=0  (no matches)
[brain-context-load] total bytes=0

$ gstack-brain-context-load --skill review --repo <slug> --explain
[brain-context-load] mode=default queries=3
  SKIP  recent-transcripts   kind=list  bytes=0  (gbrain list_pages exited 1)
  SKIP  recent-curated       kind=list  bytes=0  (gbrain list_pages exited 1)
  SKIP  skill-name-events    kind=list  bytes=0  (gbrain list_pages exited 1)
[brain-context-load] total bytes=0

Note the second case: a skill with no gbrain: manifest falls back to Layer 1, and that fallback is also all-kind: list, so it fails identically.

Source

bin/gstack-brain-context-load.ts, line 263 on current origin/main:

const cliArgs: string[] = ["list_pages", "--limit", String(limit)];
if (q.sort) cliArgs.push("--sort", q.sort);
if (q.filter) {
  for (const [k, v] of Object.entries(q.filter)) {
    cliArgs.push("--filter", `${k}=${rv}`);
  }
}
const result = spawnSync("gbrain", cliArgs, ...);

Impact

Every brain-backed query in the shipped skills is kind: list -- there are zero kind: vector queries -- and the Layer-1 fallback is all-list too. Net effect is 0 bytes of brain context in every skill, for every user with gbrain installed.

Verified independently on two machines

Machine A Machine B
gbrain 0.42.67.0 0.50.2.0
gstack 1.84.1.0 1.84.x
gbrain list_pages Unknown command Unknown command
loader --explain total bytes=0 total bytes=0

So it is not a gbrain-version artifact. On both machines the local gstack copy was unmodified (git diff origin/main -- bin/gstack-brain-context-load.ts empty) and within a few commits of origin/main.

Likely root cause, and a docs half

gbrain list --help itself says "use list_pages with sort=updated_desc" -- gbrain's CLI help refers to its own MCP tool name. Anyone writing a CLI caller from that help text lands exactly here. Worth fixing on the gbrain side too, or having the CLI accept list_pages as an alias.

Secondary mismatches (CONFIRMED on 0.50.2.0)

Initially filed as unverified leads; both are now confirmed directly:

$ gbrain list --limit 5 --filter type=timeline
gbrain list: unknown flag --filter for 'gbrain list'
Run: gbrain list --help
  • --filter does not exist. The real flags are --type and --tag. The string --filter appears nowhere in gbrain 0.50's src/cli.ts.
  • --sort value is wrong. The loader sends updated_at_desc; the CLI accepts updated_desc, updated_asc, created_desc, slug.

So fixing the verb alone is not sufficient -- the sort value and the filter flags need translating too.

Worth noting for anyone fixing this: on gbrain <=0.42 unknown flags were silently ignored, and 0.50 made them a hard error (a deliberate upstream break, per its release notes). A verb-only fix would therefore surface these two immediately on 0.50+ rather than degrading quietly.

Related but distinct

#1687 covers filter: blocks being dropped by parseSkillManifest, so kind: list queries ran unfiltered (reported fixed in #2264). That bug assumes the query executes. This one is upstream of it: with the wrong verb the command never runs at all, so correct filter parsing still yields 0 bytes.