fix: sourceLocalPath handles wrapped {sources:[...]} shape from gbrain v0.20+
Summary
bin/gstack-gbrain-sync.ts:sourceLocalPath() (added in v1.40.0.0 as part of the hostname-fold migration helpers) types the gbrain response as a flat Array<{ id, local_path }> and calls .find() on it directly. gbrain v0.20+ returns {sources: [...]} (object wrapping the array), not a flat array.
Result: /sync-gbrain crashes immediately on the orchestrator's first call to sourceLocalPath with:
gstack-gbrain-sync fatal: list.find is not a function.
(In 'list.find((s) => s.id === sourceId)', 'list.find' is undefined)
The sibling helpers probeSource and sourcePageCount in lib/gbrain-sources.ts already handle the wrapped shape correctly (parse {sources?: Array<...>}, fall back to []). This PR brings sourceLocalPath in line.
The fix accepts both shapes (flat array OR {sources: [...]}) for backward compatibility with any older gbrain still in the wild.
Reproduction
- gstack v1.40.0.0 + gbrain v0.35.0.0 on Windows (MINGW64). Likely reproduces on macOS/Linux too — this is a pure data-shape bug, not a platform issue.
- Invoke
/sync-gbrainin any repo that already has registered sources (e.g., fromgbrain sources add ... --path ...). - Orchestrator crashes before completing stage 1.
Workaround for affected users until this lands: skip the orchestrator and call gbrain sync --all --no-pull --yes directly.
Test plan
- [x] New regression test
handles {sources: [...]} wrapped shape (gbrain v0.20+)mocks the actual v0.20+ response shape and asserts the lookup succeeds. - [x] New regression test
returns null when the source is missing in the wrapped shapecovers the empty-array case. - [x] Existing tests preserved (flat-array shape still supported for backward compat).
- [ ] CI verifies on Linux (the existing
makeShiminfra uses#!/bin/shshebang scripts so the test pattern doesn't run on Windows directly; pre-existing limitation).
🤖 Generated with Claude Code
This appears to overlap with #1564: both fix sourceLocalPath() in bin/gstack-gbrain-sync.ts for the same wrapped {sources:[...]} gbrain response and the same list.find is not a function crash.
I'd treat #1564 as the canonical PR because it was opened earlier and also covers the malformed wrapped payload case with an Array.isArray(list) guard before .find(). If this PR stays active, the missing malformed-payload guard/test should be ported here; otherwise closing this as duplicate avoids two competing fixes for the same path.
Thanks @jakehann11 — sourceLocalPath fix shipped in v1.42.0.0 Daegu wave (#1594) as the chosen patch.