A5: brain-sync stage fails loud when the artifacts source indexed zero pages

The stage reported "curated artifacts pushed" whenever gstack-brain-sync
exited 0, but a git push is not gbrain indexing. After a successful push
the stage now reads the maintained artifacts source's page count
(GSTACK_BRAIN_SOURCE_ID, else the source whose path is the artifacts
worktree or state root): zero pages is ERR with the fix command; a
missing source or an unavailable count says so instead of claiming
indexing.

Co-authored-by: Jimmy Zeisweiss <4261258+jzeisweiss@users.noreply.github.com>
This commit is contained in:
garrytanandJimmy Zeisweiss committed 2026-10-03 21:19:35 +00:00
1 parent d02c8effb7
commit 16aed1ced5
3 files changed
+90 -10

No files matched your search

+41 -10
View File
@@ -214,6 +214,13 @@ export function resolveMemoryIngestSources(
* state root itself), if any. Its pages already cover FEDERATED_CURATED_TYPES.
*/
export function federatedCuratedSourceId(env?: NodeJS.ProcessEnv): string | null {
const rows = parseSourcesList(execGbrainJson(["sources", "list", "--json"], { baseEnv: env, timeout: 10_000 }));
const row = rows.find((r) => (r as { federated?: boolean }).federated === true && isArtifactsSourceRow(r, env));
return row?.id ?? null;
}
/** A source this installation maintains for its curated artifacts (the worktree or the state root). */
function isArtifactsSourceRow(r: { local_path?: string }, env?: NodeJS.ProcessEnv): boolean {
const realOrSelf = (p: string): string => {
try {
return realpathSync(p);
@@ -226,9 +233,36 @@ export function federatedCuratedSourceId(env?: NodeJS.ProcessEnv): string | null
realOrSelf((env ?? process.env).GSTACK_BRAIN_WORKTREE || join(home, ".gstack-brain-worktree")),
realOrSelf(GSTACK_HOME),
]);
const rows = parseSourcesList(execGbrainJson(["sources", "list", "--json"], { baseEnv: env, timeout: 10_000 }));
const row = rows.find((r) => (r as { federated?: boolean }).federated === true && r.local_path && owned.has(realOrSelf(r.local_path)));
return row?.id ?? null;
return !!r.local_path && owned.has(realOrSelf(r.local_path));
}
/**
* A5 (#2670): after a successful artifacts push, report what gbrain actually
* holds. A git push is not indexing; a maintained artifacts source with zero
* pages is the one provably broken state and fails the stage.
*/
export function brainSyncIndexVerdict(env: NodeJS.ProcessEnv = process.env): { ok: boolean; summary: string } {
const raw = execGbrainJson(["sources", "list", "--json"], { baseEnv: env, timeout: 10_000 });
if (raw === null) {
return { ok: true, summary: "curated artifacts pushed to git; gbrain page count unavailable (gbrain sources list failed)" };
}
const explicit = env.GSTACK_BRAIN_SOURCE_ID;
const row = parseSourcesList(raw).find((r) => (explicit ? r.id === explicit : isArtifactsSourceRow(r, env)));
if (!row?.id) {
return { ok: true, summary: "curated artifacts pushed to git (no gbrain artifacts source on this machine, so indexing is not checked here)" };
}
if (typeof row.page_count !== "number") {
return { ok: true, summary: `curated artifacts pushed to git; gbrain source ${row.id} did not report a page count` };
}
if (row.page_count === 0) {
return {
ok: false,
summary:
`curated artifacts pushed to git, but gbrain source ${row.id} has 0 indexed pages. ` +
`Fix: gbrain sync --source ${row.id}, then re-run /sync-gbrain`,
};
}
return { ok: true, summary: `curated artifacts pushed; gbrain source ${row.id} has ${row.page_count} pages` };
}
export interface MemorySourceSelection {
@@ -1598,13 +1632,10 @@ function runBrainSyncPush(args: CliArgs): StageResult {
spawnSync(discover.cmd, discover.argv, { stdio, timeout: 60 * 1000, shell: discover.shell });
const result = spawnSync(once.cmd, once.argv, { stdio, timeout: 60 * 1000, shell: once.shell });
return {
name: "brain-sync",
ran: true,
ok: result.status === 0,
duration_ms: Date.now() - t0,
summary: result.status === 0 ? "curated artifacts pushed" : `gstack-brain-sync exited ${result.status}`,
};
if (result.status !== 0) {
return { name: "brain-sync", ran: true, ok: false, duration_ms: Date.now() - t0, summary: `gstack-brain-sync exited ${result.status}` };
}
return { name: "brain-sync", ran: true, duration_ms: Date.now() - t0, ...brainSyncIndexVerdict() };
}
/**
+19
View File
@@ -408,6 +408,25 @@ gstack-gbrain-install # upgrade gbrain
---
## `curated artifacts pushed to git, but gbrain source <id> has 0 indexed pages. Fix: gbrain sync --source <id>, then re-run /sync-gbrain`
**Problem.** `/sync-gbrain`'s brain-sync stage is `ERR` even though the git
push worked.
**Cause.** The push only moves your curated artifacts to git. gbrain indexes
them separately, and the artifacts source this machine maintains reports zero
pages, so searches return nothing from it. Before this release the stage said
"curated artifacts pushed" and passed.
**Fix.**
```bash
gbrain sync --source <id>
/sync-gbrain
```
If it stays at zero, re-register the source with `gstack-gbrain-source-wireup`.
---
## Nothing is syncing but I expect it to
**Not an error, but a common gotcha.** Check in order:
+30
View File
@@ -544,6 +544,36 @@ esac
rmSync(home, { recursive: true, force: true });
});
// A5 (#2670): a successful git push is not gbrain indexing.
for (const [pageCount, ok, expected] of [
[0, false, "gbrain source fixture-artifacts has 0 indexed pages. Fix: gbrain sync --source fixture-artifacts"],
[7, true, "curated artifacts pushed; gbrain source fixture-artifacts has 7 pages"],
] as const) {
it(`brain-sync stage reports the artifacts source's ${pageCount} indexed pages (ok=${ok})`, () => {
const home = makeTestHome();
const gstackHome = join(home, ".gstack");
const fakeBin = join(home, "fake-bin");
mkdirSync(gstackHome, { recursive: true });
mkdirSync(fakeBin, { recursive: true });
writeFileSync(join(fakeBin, "gbrain"), `#!/bin/sh
case "$*" in
"sources list --json") printf '%s\\n' '{"sources":[{"id":"fixture-artifacts","local_path":"${gstackHome}","page_count":${pageCount}}]}' ;;
*) exit 1 ;;
esac
`);
chmodSync(join(fakeBin, "gbrain"), 0o755);
const r = runScript(["--incremental", "--no-code", "--no-memory", "--quiet"], {
HOME: home, GSTACK_HOME: gstackHome, PATH: `${fakeBin}:${process.env.PATH || ""}`,
});
const state = JSON.parse(readFileSync(join(gstackHome, ".gbrain-sync-state.json"), "utf-8"));
const stage = state.last_stages.find((entry: { name: string }) => entry.name === "brain-sync");
expect(stage.summary).toContain(expected);
expect(stage.ok).toBe(ok);
expect(r.exitCode).toBe(ok ? 0 : 1);
rmSync(home, { recursive: true, force: true });
});
}
it("brain-sync stage resolves the sibling binary, not a HOME-rooted path", () => {
// Regression for Codex M9: pre-fix the orchestrator looked up
// ~/.claude/skills/gstack/bin/gstack-brain-sync, which silently no-op'd