From 17dbcb8dc195ca80719193c5c319b62e9e04de84 Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Tue, 5 May 2026 07:03:21 -0700 Subject: [PATCH] fix: use correct `gbrain put ` CLI verb in memory ingest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `put_page` is the MCP tool name, not a CLI subcommand. The actual gbrain verb is `put ` with content via stdin and tags in YAML frontmatter. Every transcript / memory ingest fails today on clean installs. Switch to the right verb and inject title/type/tags into the frontmatter that buildTranscriptPage / buildArtifactPage already produce. Bundled in the same function: - timeout: 30s → 60s. Auto-link reconciliation hits 30s once the brain has a few hundred pages. - maxBuffer: 1MB → 16MB. Without it Node truncates gbrain's stderr and callers see only `Command failed:` with no detail. - Surface stderr/stdout in the returned error instead of the bare exception. Verified: bun test test/gstack-memory-ingest.test.ts -> 15/15 pass. bun test on the three test files touching this path -> 362/362. --- bin/gstack-memory-ingest.ts | 42 ++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/bin/gstack-memory-ingest.ts b/bin/gstack-memory-ingest.ts index 8ba03eb1..a359d118 100644 --- a/bin/gstack-memory-ingest.ts +++ b/bin/gstack-memory-ingest.ts @@ -763,23 +763,41 @@ function gbrainPutPage(page: PageRecord): { ok: boolean; error?: string } { if (!gbrainAvailable()) { return { ok: false, error: "gbrain CLI not in PATH" }; } + // gbrain CLI verb is `put `; it does NOT accept --title / --type / + // --tags flags. (`put_page` is the MCP tool name, not the CLI subcommand.) + // Inject title/type/tags into the YAML frontmatter that page.body already + // begins with so gbrain's frontmatter parser picks them up. + let body = page.body; + if (body.startsWith("---\n")) { + const end = body.indexOf("\n---\n", 4); + if (end > 0) { + const inject = [ + `title: ${JSON.stringify(page.title)}`, + `type: ${page.type}`, + `tags:`, + ...page.tags.map((t) => ` - ${t}`), + ].join("\n"); + body = body.slice(0, end) + "\n" + inject + body.slice(end); + } + } try { - const args = [ - "put_page", - "--slug", page.slug, - "--title", page.title, - "--type", page.type, - "--tags", page.tags.join(","), - ]; - execFileSync("gbrain", args, { - input: page.body, + execFileSync("gbrain", ["put", page.slug], { + input: body, encoding: "utf-8", - timeout: 30000, + // Bumped from 30s: auto-link reconciliation on dense transcripts hits + // 30s once the brain has a few hundred existing pages. + timeout: 60000, + // Bumped from default 1MB: without this, gbrain's actual stderr gets + // truncated and callers see only "Command failed:" with no detail. + maxBuffer: 16 * 1024 * 1024, stdio: ["pipe", "pipe", "pipe"], }); return { ok: true }; - } catch (err) { - return { ok: false, error: err instanceof Error ? err.message : String(err) }; + } catch (err: any) { + const stderr = err?.stderr?.toString?.() ?? ""; + const stdout = err?.stdout?.toString?.() ?? ""; + const detail = stderr || stdout || (err instanceof Error ? err.message : String(err)); + return { ok: false, error: detail.split("\n")[0].slice(0, 300) }; } }