Merge remote-tracking branch 'origin/capy/fw3-a1' into capy/fw3-a2

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

# Conflicts:
#	scripts/harness-version.json
This commit is contained in:
garrytan committed 2026-10-18 00:39:35 +00:00
commit 90f2148752
75 files changed
+4068 -236

No files matched your search

+1
View File
@@ -44,6 +44,7 @@ cause, the held files and the fix.
| `error` | The drain stopped before committing and kept the queue | `status=error. The last sync stopped before committing; the queue is kept and retried. See the cause: <bin> --status` | The cause `--status` names: a git index lock, a commit hook, a `git add` failure |
| `unknown` | The status file holds a status code outside this table | `status=unknown. The sync status file is unreadable. See it: <bin> --status` | Run `<bin> --once` to rewrite it |
| stale-push | No push for 24 hours while the drain's `drainable` count is above zero | `stale-push. Files are ready to sync but nothing has been pushed for over 24 hours. See why: <bin> --status` | The waiting reason `--status` names (a coupled group waiting for a consistent generation, a `git add` failure) |
| case-collision | `case_names` is set: artifact paths differ only by letter case, so the drain holds queued files under them | `case-collision held=<N> names=<names>. Artifact paths differ only by letter case, which a Mac or Windows disk stores as one file; sync holds <N> queued file(s) under them and syncs everything else. Repair: https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#artifact-case-collision. See the paths: <bin> --status` | Keep one spelling: the manual repair in [troubleshooting](troubleshooting.md#artifact-case-collision) |
| stale-drain | `last_drain_at` older than 24 hours while the queue holds records | `stale-drain. The sync has not run for over 24 hours while files wait in the queue. See why: <bin> --status` | If no gstack process is running, remove `~/.gstack/.brain-sync.lock.d`, then run `<bin> --once` |
`<bin>` is the absolute path, for example
+5 -2
View File
@@ -90,8 +90,11 @@ On machine B:
gstack-brain-sync --status
```
Shows: the status (`ok`, `idle`, `held`, `blocked`, `push_failed`,
`error`), files held back by the secret scan with their fixes, the
Shows: the status (`ok`, `idle`, `held`, `collision`, `blocked`,
`push_failed`, `error`), files held back by the secret scan with their
fixes, files held because their path differs only by letter case from
another path (`case_held`, `case_names`; see
[troubleshooting](troubleshooting.md#artifact-case-collision)), the
`drainable` count, the last sync and push times, pending queue depth, and
the current privacy mode.
+72
View File
@@ -630,6 +630,78 @@ has nothing to calibrate from. It no longer says "calibrated".
**Fix.** Answer more registered questions, then `gstack-developer-profile --derive`.
<a id="artifact-case-collision"></a>
### `ARTIFACTS_SYNC: attention: case-collision held=<N> names=<names>. Artifact paths differ only by letter case, ...`
**Meaning.** The artifacts repo holds paths that differ only by letter case,
such as `projects/MyApp/` and `projects/myapp/`. A Mac or Windows disk stores
them as one file, so git there would stage a write under the other spelling,
and sync would report success while one spelling stays modified and the other
machines read stale data (#3220). Sync now holds every queued file under such a
path instead of staging it: the file stays queued, everything else keeps
syncing, and the status is `collision` rather than `ok`. When only one
spelling is tracked, that spelling keeps syncing and only the new alias is
held; when neither is tracked yet, both wait. `<names>` are the colliding folder or file names; `gstack-brain-sync
--status` lists the held paths in `message`. A local commit that would push a
new alias to the remote (two machines created both spellings at once) is
refused with status `error` and kept locally.
Nothing is repaired automatically. Slug resolution and skill start are
unaffected; only the sync holds files.
**Fix.** Keep one spelling, by hand, on one machine:
1. Choose the spelling to keep: the one `gstack-slug` prints in that project
today. If the project resolves to different spellings (a remote URL typed
two ways, a folder reached as `~/code/MyApp` and `~/code/myapp`), make it
stable first: fix the remote URL's case, or set `GSTACK_PROJECT_SLUG`.
2. Fold the other spelling's tracked files into the kept one. Read both
spellings from git, because a Mac's working tree shows only one of them,
and the working files too, because they hold the held, unsynced writes.
This loop covers the dropped spelling's tracked files and its untracked
ones (held writes that never synced). It folds JSONL logs (concatenated,
duplicate lines dropped) and prints every other file for you to decide:
```bash
cd ~/.gstack
keep=projects/MyApp; drop=projects/myapp
{ git -c core.ignorecase=false ls-files -- "$drop"; git -c core.ignorecase=false ls-files --others -- "$drop"; } | sort -u | while IFS= read -r f; do
rel=${f#"$drop"/}
case "$rel" in *.jsonl) ;; *) echo "decide by hand: $f"; continue ;; esac
{ git show ":$keep/$rel"; git show ":$f"; cat "$keep/$rel" "$f"; } 2>/dev/null | awk '!seen[$0]++' > "$HOME/.gstack-fold.tmp"
mkdir -p "$keep/$(dirname "$rel")" && mv "$HOME/.gstack-fold.tmp" "$keep/$rel"
git -c core.ignorecase=false add -f -- "$keep/$rel"
git -c core.ignorecase=false rm -q --cached --ignore-unmatch -- "$f"
echo "folded: $keep/$rel"
done
```
For each file it prints (Markdown, JSON snapshots such as
`decisions.active.json`), compare `git show ":$keep/<file>"` with
`git show ":$drop/<file>"`, write the content to keep to `"$keep/<file>"`,
then run the same `git -c core.ignorecase=false add -f -- "$keep/<file>"`
and `git -c core.ignorecase=false rm -q --cached --ignore-unmatch -- "$drop/<file>"`. Never
concatenate a JSON snapshot. Then commit and push:
```bash
git -c core.ignorecase=false commit -m "repair: fold $drop into $keep"
git push origin HEAD
```
On Linux the two folders are separate on disk: after the commit, check
that `git -c core.ignorecase=false ls-files --others -- "$drop"` lists
nothing you have not folded or moved, then remove the dropped folder
(`rm -r "$drop"`). On a Mac or Windows they are the same folder, so do
not remove anything.
3. Clear the held records of the dropped spelling and queue the kept files:
`gstack-brain-sync --skip-file <dropped path>` for each held path that
`--status` lists, then `gstack-brain-enqueue <kept path>` for each
`folded:` line and each file you moved by hand, and
`gstack-brain-sync --once`.
4. On every other machine, let the next skill start pull the repair (or run
`git -C ~/.gstack pull`). The line disappears once no tracked path
differs only by case.
<a id="skill-start-former-bucket"></a>
### `gstack: earlier data for this project is in projects/<slug> (a worktree's old bucket). Merge it: ...`