fix(brain-sync): make artifact sync work on Windows (discover-new + drain)#1672
Open
daveowenatl wants to merge 1 commit into
Open
fix(brain-sync): make artifact sync work on Windows (discover-new + drain)#1672daveowenatl wants to merge 1 commit into
daveowenatl wants to merge 1 commit into
Conversation
ab13b72 to
dee462b
Compare
…rain) Automatic artifact sync was fully non-functional on Windows (Git Bash): --discover-new enqueued nothing and the --once drain staged nothing, so artifacts_sync_mode looked active but no artifacts ever reached the repo. Three independent Windows-only causes in bin/gstack-brain-sync: 1. discover-new matched os.path.relpath (backslash separators on Windows) against the forward-slash allowlist globs, so no nested file ever matched. Normalized the relpath to "/". 2. discover-new enqueued via subprocess.run([gstack-brain-enqueue, rel]), but Windows Python cannot exec a bash-shebang script, so nothing was enqueued even once matched. Now appends to the queue in-process. 3. compute_paths_to_stage ends in print(p); Windows Python emits CRLF, the bash `read -r` keeps the trailing CR, and `git add -- "path<CR>"` matches nothing under `2>/dev/null || true`. Now strips the CR before staging. The in-process enqueue mirrors gstack-brain-enqueue's contract: one atomic O_APPEND write per record (each line < PIPE_BUF) so a parallel writer-shim append can't interleave mid-record, and the discover cursor advances only after the write succeeds, so a failed write retries instead of silently recording the file as synced. Skip-list entries are separator-normalized on both the discover and drain (compute_paths_to_stage) sides, so a backslash .brain-skip.txt entry can't be honored at discovery yet bypassed at commit. Adds test/brain-sync-windows-paths.test.ts (static invariants -- behavioral spawn tests cannot run on the Windows lane, since Node/Bun cannot exec the bin/ shebang scripts there) and wires it into windows-free-tests.yml. Verified red->green and end-to-end on Windows 11 / Git Bash; macOS/Linux behavior unchanged (os.sep is already "/", no CRLF, compute path logic unchanged besides the shared skip normalization). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
dee462b to
f4191bb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automatic artifact sync (
gstack-brain-sync) is fully non-functional on Windows (Git Bash):--discover-newenqueues nothing and the--oncedrain stages nothing, soartifacts_sync_mode=fulllooks active but no artifacts ever reach the repo. No error surfaces — status reportsidle.Environment
Windows 11 (26200), Git Bash (Git for Windows 2.54.0), Bun 1.3.11, Python 3.14.3 (
python3on PATH), gstack v1.43.3.0.Root causes (three, all in
bin/gstack-brain-sync)os.path.relpathreturns backslash paths on Windows, which never match the forward-slash allowlist globs (projects/*/learnings.jsonl). Fixed by normalizingos.sep→/.subprocess.run([gstack-brain-enqueue, rel]), but Windows Python can't exec a bash-shebang script, so--discover-newleft the queue empty even when files matched. Now appends to the queue in-process. Same "spawnSync can't exec bash on Windows" family as fix: sourceLocalPath handles wrapped {sources:[...]} shape from gbrain v0.20+ #1571 / fix(sync-gbrain): Windows compat — capability check + brain-sync skip #1641, different code path.compute_paths_to_stageends inprint(p); Windows Python emits CRLF, the bashwhile IFS= read -r pkeeps the trailing CR, andgit add -f -- "path<CR>"fails withpathspec '…?' did not matchunder2>/dev/null || true. Nothing is staged, the commit is empty, status reportsidle. Now strips the trailing CR beforegit add.The in-process enqueue mirrors
gstack-brain-enqueue's contract rather than re-deriving it loosely: one atomicO_APPENDwrite per record (each line < PIPE_BUF) so a parallel writer-shim append can't interleave mid-record, and the discover cursor advances only after the write succeeds (a failed write retries instead of silently recording the file as synced). Skip-list entries are separator-normalized on both the discover and drain (compute_paths_to_stage) sides, so a backslash.brain-skip.txtentry can't be honored at discovery yet bypassed at commit — the drain is the boundary that actually stages files.Repro
Testing
test/brain-sync-windows-paths.test.ts— static invariants guarding all three causes plus the two robustness properties (atomic per-record append, normalized skip matching). Behavioral spawn tests can't run on the Windows lane (Node/Bun can't exec thebin/shebang scripts — the same reasonbrain-sync.test.tsis excluded from it), so this follows the static-invariant pattern ofbuild-script-shell-compat.test.ts. Verified red→green. Wired intowindows-free-tests.yml.sync: N file(s)with nested and single-level artifacts; non-allowlisted files excluded; a backslash.brain-skip.txtentry correctly keeps its file off the remote; queued records are valid compact JSONL; re-discover is idempotent.os.sepis already/so both separator-normalizations are no-ops, there's no CRLF to strip, and the inlineO_APPENDappend is equivalent to the previous per-file enqueue.Related (not in this PR)
bin/gstack-brain-restore:117has the same relpath+fnmatch bug in its clobber check — happy to follow up separately.🤖 Generated with Claude Code