Versions
- gstack:
1.40.0.0
- gbrain CLI:
gbrain0.35.7.0
- Affected file:
bin/gstack-gbrain-sync.ts
Symptom
Running /sync-gbrain (or bun bin/gstack-gbrain-sync.ts --full) when the migration planner inspects a legacy source:
[gbrain-sync] mode=full engine=supabase
gstack-gbrain-sync fatal: list.find is not a function. (In 'list.find((s) => s.id === sourceId)', 'list.find' is undefined)
Exit code 1, orchestrator aborts before any stage runs.
Root cause
sourceLocalPath() (lines 291-299) types the gbrain sources list --json response as a bare array:
const list = execGbrainJson<Array<{ id: string; local_path?: string }>>(
["sources", "list", "--json"],
{ baseEnv: env },
);
if (!list) return null;
const found = list.find((s) => s.id === sourceId);
But the CLI actually returns the array wrapped in a sources key:
{
"sources": [
{ "id": "default", "page_count": 114, ... },
{ "id": "gstack-artifacts-kennedy", "page_count": 0, ... }
]
}
So list is the wrapper object, list.find is undefined, and the orchestrator crashes.
The two sibling helpers in lib/gbrain-sources.ts (probeSource at line 51 and sourcePageCount at line 162) already unwrap .sources correctly — only sourceLocalPath was missed.
Fix (three-line patch)
```diff
export function sourceLocalPath(sourceId: string, env?: NodeJS.ProcessEnv): string | null {
- const list = execGbrainJson<Array<{ id: string; local_path?: string }>>(
- const response = execGbrainJson<{ sources?: Array<{ id: string; local_path?: string }> }>(
["sources", "list", "--json"],
{ baseEnv: env },
);
- if (!response) return null;
- const list = response.sources ?? [];
const found = list.find((s) => s.id === sourceId);
return found?.local_path ?? null;
}
```
Trigger conditions
sourceLocalPath is called from planHostnameFoldMigration whenever the hostname-fold migration runs (legacy path-only-hash source id differs from the new hostname-fold id). Anyone upgrading from a pre-#1468 install hits it on their next /sync-gbrain run.
Suggested test
Add a fixture for execGbrainJson returning {sources: [{id:"x", local_path:"/p"}]} and assert sourceLocalPath("x") returns "/p" — current code returns null even before the throw on real input.
Versions
1.40.0.0gbrain0.35.7.0bin/gstack-gbrain-sync.tsSymptom
Running
/sync-gbrain(orbun bin/gstack-gbrain-sync.ts --full) when the migration planner inspects a legacy source:Exit code 1, orchestrator aborts before any stage runs.
Root cause
sourceLocalPath()(lines 291-299) types thegbrain sources list --jsonresponse as a bare array:But the CLI actually returns the array wrapped in a
sourceskey:{ "sources": [ { "id": "default", "page_count": 114, ... }, { "id": "gstack-artifacts-kennedy", "page_count": 0, ... } ] }So
listis the wrapper object,list.findisundefined, and the orchestrator crashes.The two sibling helpers in
lib/gbrain-sources.ts(probeSourceat line 51 andsourcePageCountat line 162) already unwrap.sourcescorrectly — onlysourceLocalPathwas missed.Fix (three-line patch)
```diff
export function sourceLocalPath(sourceId: string, env?: NodeJS.ProcessEnv): string | null {
["sources", "list", "--json"],
{ baseEnv: env },
);
const found = list.find((s) => s.id === sourceId);
return found?.local_path ?? null;
}
```
Trigger conditions
sourceLocalPathis called fromplanHostnameFoldMigrationwhenever the hostname-fold migration runs (legacy path-only-hash source id differs from the new hostname-fold id). Anyone upgrading from a pre-#1468 install hits it on their next/sync-gbrainrun.Suggested test
Add a fixture for
execGbrainJsonreturning{sources: [{id:"x", local_path:"/p"}]}and assertsourceLocalPath("x")returns"/p"— current code returnsnulleven before the throw on real input.