generate_exclude cannot tell an operator's hand-edit from a config change, so it refuses both
scripts/sync-workspace.sh::generate_exclude compares .git/info/exclude against the
content it would generate. If they differ and --force-gitignore is absent it warns,
prints the diff, and returns 1:
sync-workspace: <path> EXISTS and DIFFERS from the generated content.
Refusing to overwrite (operator-authored content may block carrier-set paths).
The guard's stated purpose is protecting operator-authored content. But a config edit is
exactly what makes the two differ, so the refusal fires on every legitimate
vault.sync.include change too. The two cases are indistinguishable to it.
Measured consequence
Adding talks/ to vault.sync.include on this host had no effect for ~20 minutes. The
exclude was never rewritten, talks/ stayed ignored, and the vault kept not backing it up.
Nothing surfaced it except the carrier-set health probe:
✗ carrier-set 1 configured carrier path(s) are STILL GIT-IGNORED so the vault is not
backing them up (talks/) — the exclude file is stale and sync refused to
regenerate it
Sync's own stdout for that tick was just sync-workspace: pushed to host/<host>/<wsid>.
The refusal goes to the sync log, which on a cron nobody reads. So the failure mode is: a
config edit reads as applied, does nothing, and the only signal is a probe the operator
may not run.
bash scripts/sync-workspace.sh --force-gitignore fixed it — after confirming all 46
exclude lines matched the generated structure, so nothing hand-authored was at risk.
Why this is worth fixing rather than documenting
The check is comparing the wrong two things. It asks "does the file differ from what I
would generate now?", which conflates:
- the file was hand-edited (the case it wants to catch), and
- the config changed since the file was generated (a case it should just apply)
Recording a digest of the content as generated — alongside the file, or as a comment
line in it — would separate them: file matches its recorded digest → regenerate freely;
file diverges from its digest → an operator touched it, refuse as today. The header
already declares # Generated by sync-workspace.sh — do not edit by hand, so a digest
line is consistent with what the file already claims about itself.
Peer-visible effect
A second host hit the same guard from the other side: it hand-merged !talks/ +
!talks/** rather than use --force-gitignore, and that only worked because what it typed
happened to be byte-identical to what the generator emits. Anything else would have marked
the file as operator-authored permanently, making every later carrier edit on that host
inert.
Reported by Sutando-Pro; measured independently here.
generate_excludecannot tell an operator's hand-edit from a config change, so it refuses bothscripts/sync-workspace.sh::generate_excludecompares.git/info/excludeagainst thecontent it would generate. If they differ and
--force-gitignoreis absent it warns,prints the diff, and returns 1:
The guard's stated purpose is protecting operator-authored content. But a config edit is
exactly what makes the two differ, so the refusal fires on every legitimate
vault.sync.includechange too. The two cases are indistinguishable to it.Measured consequence
Adding
talks/tovault.sync.includeon this host had no effect for ~20 minutes. Theexclude was never rewritten,
talks/stayed ignored, and the vault kept not backing it up.Nothing surfaced it except the
carrier-sethealth probe:Sync's own stdout for that tick was just
sync-workspace: pushed to host/<host>/<wsid>.The refusal goes to the sync log, which on a cron nobody reads. So the failure mode is: a
config edit reads as applied, does nothing, and the only signal is a probe the operator
may not run.
bash scripts/sync-workspace.sh --force-gitignorefixed it — after confirming all 46exclude lines matched the generated structure, so nothing hand-authored was at risk.
Why this is worth fixing rather than documenting
The check is comparing the wrong two things. It asks "does the file differ from what I
would generate now?", which conflates:
Recording a digest of the content as generated — alongside the file, or as a comment
line in it — would separate them: file matches its recorded digest → regenerate freely;
file diverges from its digest → an operator touched it, refuse as today. The header
already declares
# Generated by sync-workspace.sh — do not edit by hand, so a digestline is consistent with what the file already claims about itself.
Peer-visible effect
A second host hit the same guard from the other side: it hand-merged
!talks/+!talks/**rather than use--force-gitignore, and that only worked because what it typedhappened to be byte-identical to what the generator emits. Anything else would have marked
the file as operator-authored permanently, making every later carrier edit on that host
inert.
Reported by Sutando-Pro; measured independently here.