Repository navigation
fix(sync-workspace): refuse push/pull when .git present but sync uninit (Lucy's Bug #4) - #1483
Merged
qingyun-wu merged 1 commit intoJun 6, 2026
Conversation
…it (Lucy's Bug #4) Lucy's Maddy v0.8 migration report (2026-06-06 #design): plain `bash scripts/sync-workspace.sh` silent-committed the entire workspace tree on a Maddy install whose <workspace>/.git existed but where sync was never properly initialized — no `.git/info/exclude` whitelist marker, no `.sutando-vault/ws-id`. `git add -A` then staged every file including credentials, media, vendor caches. Two-tier guard: 1. **First tier (already present)**: `.git` directory presence check at the top of `_pull_only_impl` + `_push_only_impl`. Dies with "is not a git repo; run --init first" when `.git` is absent. 2. **Second tier (this fix)**: `_assert_sync_initialized` runs AFTER the `.git` check. Accepts EITHER sentinel as proof of real init: - `.git/info/exclude` contains "Generated by sync-workspace.sh" marker - `.sutando-vault/ws-id` exists (PR #1459) If neither is present, dies with a clear error pointing to `--init`: "$WORKSPACE_DIR has .git but sync was never initialized (no whitelist marker ... and no .sutando-vault/ws-id). Refusing to push — git add -A here would commit the WHOLE workspace tree with NO carrier-set filter." Override for operators on legacy pre-marker inits: `SUTANDO_SYNC_SKIP_INIT_GUARD=1` Tests: `tests/sync-workspace-uninit-guard.test.sh` - 7 structural assertions: helper declared, called in both impls, override honored, marker + ws-id grep present, guard runs AFTER the .git presence check (right error message on .git-absent) - 4 function-logic E2E: uninit fails, marker passes, ws-id passes, override bypasses. Function extracted to a test-local shell to avoid sourcing the script (which would trigger dispatcher + acquire the global lock). Bug #4 of 5 from Lucy's Maddy migration report. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sonichi
approved these changes
Jun 6, 2026
sonichi
left a comment
Owner
There was a problem hiding this comment.
LGTM. Two-tier guard is the right design: SKIP_INIT_GUARD bypass → exclude-with-signature check → ws-id check → die with actionable error. The die message names the exact safety risk ('git add -A would commit the WHOLE workspace tree with NO carrier-set filter') and the exact fix ('run --init'). Lucy validated all 3 cases including a real hit on Studio's actual workspace (.git without init markers). CI pending but logic is solid. Stand: Echo Act IV Pro
4 tasks
Merged
7 tasks
qingyun-wu
added a commit
that referenced
this pull request
Jun 6, 2026
…rator checklist + workspace-sync node_modules Per Chi's PR #1472 comments (#design 2026-06-06): **CHANGELOG.md v0.3.0 additions:** - Fixed → Migration subsection: Lucy's Maddy v0.8 5-bug sweep (#1475 #1478 #1482 #1483 #1484) - Fixed → Cross-platform: #1476 (uname -s branch for preflight byte count) - Fixed → Security: #1452 #1462 #1466 (execSync → execFileSync sweep) - Added: #1467 (bridges inject skill instructions into owner task files) - Workspace contract rollup → M1: also reference #1473 (pre-flight scan + per-file progress reporter) **KNOWN_ISSUES.md additions** (from migration-thread digest 1512359988834730014): - Pre-migration operator checklist (7 items) — vault URL, remove shell-rc env var, grep .git/info/exclude, confirm wsId, stagger hosts, restart services, post-migration sanity check - Identity-collision rule — exactly one host at a time can hold a Discord token (also applies to Twilio + other per-identity creds) **docs/workspace-sync.md** pre-flight checklist: - Added `node_modules/` row per Chi's PR #1472 comment (covers vault-sync side; #1482 already handles the backup-side via `_BACKUP_DEFAULT_EXCLUDES`) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
qingyun-wu
added a commit
that referenced
this pull request
Jun 6, 2026
…ni's 3 doc catches
Two concerns, one commit per owner directive (task-1780750027567 +
task-1780749875243):
### CI fix (host-cli-dependency-snapshot)
`src/task-bridge.ts:261` hardcoded `process.env.CLAUDE_CONFIG_DIR ||
join(homedir(), '.claude')` — anti-pattern caught by
`tests/host-cli-dependency-snapshot.test.py`. Replaced with
`claudeHomePath('skills', 'video-concat', 'scripts', 'prepend-image.sh')`
from `src/util_paths.ts`, which already handles
$CLAUDE_CONFIG_DIR → $CLAUDE_HOME → ~/.claude/ precedence with the
documented deprecation warning. Also dropped the now-unused `homedir`
import.
### Mini's 3 catches on PR #1472 docs (#design 2026-06-06 12:44Z)
3 of Mini's 4 catches; #1 (`KNOWN_ISSUES.md` L7 contradiction) already
shipped in `102d318`.
#2 — `docs/workspace-sync.md` L155-158 listed three "Known issues from
prior migrations" (Claude memory not auto-moved, sync-workspace plain
run silently commits, SUTANDO_WORKSPACE re-migrate-every-boot) that are
ALL shipped-as-fixed in v0.3.0 per the CHANGELOG (#1475, #1483, #1478
respectively). Section relabeled "What used to break (resolved in
v0.3.0 — historical reference)" with each bullet rewritten as a fixed-by
breadcrumb linking to the relevant PR.
#3 — `docs/workspace-sync.md` L60-62 framed sync rules as living in
the workspace's `.gitignore` with `.git/info/exclude` as parenthetical
alternative. Per #1460, the carrier writes its sync rules to
`.git/info/exclude` EXCLUSIVELY — `.gitignore` is operator-tracked and
must stay clean of carrier-set rules. Flipped the framing: lead with
`.git/info/exclude`, with explanation of why `.gitignore` is the wrong
place.
#4 — `docs/workspace-sync.md` L146 said `bash src/health-check.py` —
the file is Python, not shell. Changed to `python3 src/health-check.py`.
Stand: Echo Act IV (Mini) ready to LGTM once these land.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
qingyun-wu
added a commit
that referenced
this pull request
Jun 8, 2026
…(submodule case) (#1546) Two compounding bugs let `sync-workspace.sh --init` operate on a PARENT repo's worktree when the workspace is nested inside another git repo (e.g. a git submodule): 1. generate_exclude ran `mkdir -p .git/info` even under --dry-run, leaving a STUB `.git/` (lone `info/`, no HEAD). A later --init saw `.git` present and skipped `git init`. 2. _init_impl decided "already a repo" from a bare `-d .git` check, which the stub passes. With no real repo in the workspace, git walked UP to the parent worktree, so every remote/add/commit/push hijacked it: rewrote its origin to the vault URL, committed its whole tree, and pushed that to the vault. Observed on a Mac mini where the sutando checkout is a submodule of an outer repo and the workspace is the in-repo `<repo>/workspace`. A clean `git init` isolates fine even when nested (verified) — the bug was the stub plus the dir-existence-only check. Fix: - generate_exclude no longer mkdirs under --dry-run (a dry-run must never mutate state). - _init_impl decides isolation by the resolved toplevel (`git rev-parse --show-toplevel` -ef "$WORKSPACE_DIR"): it git-inits when the dir is not its own toplevel, and a post-init fail-safe `die`s if the fresh repo still resolves to a parent — refusing rather than corrupting it. This also makes the nested/submodule layout a supported config. Complements the push/pull guard from #1483 (which covers _push_only_impl / _pull_only_impl, not _init_impl). Tests: tests/sync-workspace-init-isolation.test.sh — 4 structural + 6 E2E (reproduce the stub/nested hijack, assert the parent repo is untouched). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Summary
Closes Bug #4 of 5 from Lucy's Maddy v0.8 migration report (2026-06-06 #design).
Maddy ran plain `bash scripts/sync-workspace.sh` on a workspace where `/.git` existed but the sync layer was never initialized — no `.git/info/exclude` whitelist marker, no `.sutando-vault/ws-id`. Result: `git add -A` staged the ENTIRE workspace tree (credentials, media, vendor caches) and pushed it.
Two-tier guard
Tier 1 (already present): `.git` presence check at the top of `_pull_only_impl` + `_push_only_impl`. Dies with `is not a git repo; run --init first` when `.git` is absent.
Tier 2 (this fix): `_assert_sync_initialized` runs AFTER the tier-1 check. Accepts EITHER sentinel as proof of real init:
If neither is present:
```
push-only: has .git but sync was never initialized (no whitelist marker in .git/info/exclude and no .sutando-vault/ws-id). Refusing to push — git add -A here would commit the WHOLE workspace tree with NO carrier-set filter. Run: bash scripts/sync-workspace.sh --init (or set SUTANDO_SYNC_SKIP_INIT_GUARD=1 to bypass at your own risk)
```
Override
`SUTANDO_SYNC_SKIP_INIT_GUARD=1` for operators on a legacy pre-marker init.
Tests
`tests/sync-workspace-uninit-guard.test.sh`:
Function extracted into a test-local shell to avoid sourcing the script (which would trigger the dispatcher + acquire the global `/tmp/sync-workspace.lock.d` lock — would collide with any concurrent host cron). The structural tests prove the wiring; the function-logic tests prove the logic.
ALL TESTS PASS.
Diff stats
2 files changed, +165 lines (36 in sync-workspace.sh, 129 test).
Related
v0.3.0 milestone table
Test plan
🤖 Generated with Claude Code