Repository navigation
GitSync: a source that never converges re-clones its WHOLE repository on every green build — lastSyncCommitSha is both the cheapness gate and the convergence guard #3945
Description
Summary
GitHubWebhookProcessor.SkipReason makes a green-build delivery free for exactly one reason —
lastSyncCommitSha == headSha — and GitHubSyncService.MayAdvanceBaseline deliberately holds
that field whenever an import did not fully converge (Preserved > 0, Failed > 0, or outcome
Failed). Both behaviours are individually correct and each has an incident behind it (#675/#677
for the horizon, #2229 item C for the sha). Together they mean:
A sync source that cannot converge pays a full
git fetch --depth 1of its entire repository,
plus a full parse, on EVERY publish-signal delivery — and the rate is the source repository's CI
cadence, not anything about the source.
The allow-list that admits schedule and repository_dispatch states the opposite as its
justification, in PublishSignalTriggers:
Widening this cannot cause churn. A sync source already sitting on the built sha is skipped by
SkipReason("already at this commit"), so a scheduled or dispatched re-verification of an
unchanged default branch triggers no import at all.
That invariant holds for every source that converges and for no source that does not — and the
sources that do not converge are precisely the ones whose imports are most expensive.
Measured
Trigger rate — Systemorph/MeshWeaver main, 24 h ending 2026-09-10T17:35Z: 254 green
publish-signal runs (gh api repos/Systemorph/MeshWeaver/actions/runs?branch=main&status=success),
of which 87 Prod synthetic probe (schedule, cron: "*/15 * * * *" — an HTTP probe that builds
nothing), 47 Chart Gate, 47 Hosting Operator, 46 MeshWeaver Build and Test (all push), 19
Continuous Delivery (main), 8 other. Independently confirmed by the node the webhook writes:
Admin/_Build/Systemorph.MeshWeaver on memex.meshweaver.cloud read version 4778 at
2026-09-10T17:21Z, first written 2026-08-06T07:38:37Z — 4,775 deliveries in 35 days, 256 in the
last 24 h (10.7/h). So one merge is ~3 deliveries at the same sha, plus 96/day from a cron whose
head_sha does not move between merges.
Two live frozen sources (all 67 _GitSync nodes on memex.meshweaver.cloud read 2026-09-10
17:30–17:39Z; search returned 67, truncated:false; 67 read, 0 unreadable):
| Source | Repo / subdir | lastSyncedAt |
lastSyncAttemptAt |
Outcome | Gap |
|---|---|---|---|---|---|
Essentials/_GitSync |
MeshWeaver.Plugins / Essentials |
2026-08-10T07:23:48Z | 2026-09-10T16:10:01Z | Imported |
31 d |
Deployments/_GitSync |
Memex / mesh/Deployments |
2026-09-09T01:10:46Z | 2026-09-10T15:27:00Z | Imported |
1 d 14 h |
Essentials is on lastSyncCommitSha: 709e7307…, ahead_by: 436, behind_by: 0 (still an
ancestor — nothing force-pushed), with ≥11 unimported files under Essentials/. It is the only one of the 34 sources on that
repository off the shared baseline: its 33 siblings all carry f4570459… with lastSyncedAt
equal to lastSyncAttemptAt. Same repo, same webhook, same schedule — the difference is convergence.
The independent node at Deployments/_GitSync on memex.systemorph.com reads
lastSyncedAt: 2026-08-19T20:06:37Z against lastSyncAttemptAt: 2026-09-10T15:27:00Z — 22 days
of attempts that never reconciled. It is the only source in either set carrying twoWay: true, i.e.
the Preserved > 0 arm.
None of the three freeze conditions clears itself: a preserved node stays preserved until someone
commits it back or discards it; a node refused on a content rule is refused identically on the same
bytes for ever; an import that failed outright fails again until its cause is fixed.
The cost of one non-skipped delivery
GitHubSyncService.FetchAndImport fetches first and diffs second — it has to, because
GetChangedPaths needs snapshot.CommitSha:
repoClient.Fetch(...) → git init; git fetch --depth 1; git checkout --detach; read worktree
ParseSnapshot(...) → every file materialised as a MeshNode
repoClient.GetChangedPaths(...)→ the diff — only now
StaticRepoImporter.ImportSource(..., changedNodePaths) ← the diff scopes THIS
So the git-diff scope is a write scope, never a fetch scope, and there is no sparse-checkout or
partial-clone filter: a configured subdirectory is applied while READING the worktree
(GitProtocolRepoClient.Fetch), so a source watching a four-file subdirectory still clones the whole
repository. Essentials has been doing that against MeshWeaver.Plugins on every green Plugins build
since 2026-08-10.
What #3146 already closes, and what it does not
The content-addressed marker turns a second pass at an unchanged fingerprint into Skipped, which
carries Failed == 0, so the baseline finally advances and the webhook gate re-arms — "every
delivery" becomes "twice per new commit". Two limits:
- It engages after the fetch. The marker id is the fingerprint of the parsed source, so the
clone and the parse are already paid. It saves the write fan-out and the recompiles, not the
transfer. - It needs the pass to be ALL-deterministic.
IsContentVerdictis a deliberately short
allow-list (InvalidPath,InvalidNodeType,ValidationFailed,Unauthorized); everything else
stays retryable because getting that backwards is A Space whose content sync is refused stops syncing SILENTLY — AgenticEngineering's assets are over the frame limit outright #3101. OneUnknownamong four hundred content
verdicts keeps the outcomeImportedWithErrors, keeps the markerWarning, and keeps the
partition re-importing on every delivery. - It does nothing at all for the
Preserved > 0arm, which is theEssentialsandDeployments
case.
Proposed fix (needs a decision — deliberately not implemented here)
The engine has no way to say "we have already looked at exactly these bytes for this source"
independently of "the mesh is known to hold this commit". One field answers both questions and the
two answers must diverge.
Sketch: record the commit the last attempt targeted — a new LastAttemptedCommitSha, stamped on
every conclusion beside the existing LastSyncAttemptAt / LastSyncOutcome, which nothing branches
on today — and add a fifth SkipReason: the last attempt for this source already reached a verdict
at this exact commit. LastSyncCommitSha and LastSyncedAt keep their present, conservative
semantics, so #675 / #677 / #2229 item C are untouched: the next NEW commit still triggers a full
unscoped import and the preserved/failed nodes get another chance then.
The regression this must not cause, and why it needs a decision rather than a patch: today a
genuinely transient failure (Unknown — "Persistence read failed…", "Inner CreateNode faulted…") is
retried by the next delivery, which on core is minutes away. Under the sketch it would wait for the
next commit. That accidental retry loop is load-bearing for exactly the failures the classifier
refuses to call final, so the change wants either a bounded re-attempt of its own or a carve-out for
non-deterministic outcomes. Both are design choices about the sync engine of a live fleet, which is
why this is filed rather than shipped.
Documentation
The mechanism, the measurements above and the read-only check for a frozen source are written up in
Doc/Architecture/GitSyncTriggerCost — What a Green Build Costs a Synced Space.
Related
Systemorph/Memex#237 (the live worst case: a MeshWeaver/_GitSync on memex.meshweaver.cloud
pointed at https://github.com/Systemorph/MeshWeaver with no subdirectory, 389 permanently-refused
nodes per pass) · #675 · #677 · #2229 · #3101 · #3146 · #3581 · #3589
Activity
rbuergi commentedon Sep 10, 2026
The engine half is #3974.
The sketch, with the refinement the regression note demanded. A second, weaker pointer —
lastAttemptedCommitSha— plus alastAttemptWasFinalflag, because the sha alone would haveturned today's accidental retry loop off.
lastSyncCommitShaandlastSyncedAtare untouched, so#675 / #677 / #2229 item C are exactly as conservative as they were.
The classifier the flag reads is
StaticRepoImportResult.VerdictIsFinal, deliberately adifferent question from
Converged:Converged(is the partition equal to the source?)VerdictIsFinal(would running again change the answer?)Preserved > 0, nothing failedImportedWithContentErrorsImportedWithErrorsFailedPruneRefusedPreserved > 0is the row that matters live — it is the arm both frozen sources sit on, and the onethe #3146 marker does nothing for. The pair is also cleared by an export (which advances the
conflict horizon, changing what a re-import would preserve) and by a hold (no attempt ran, and a
seal that arrives later is the archetype of a condition that clears without the commit moving).
IsContentVerdict's exact population, and what it does not coverInvalidPath,InvalidNodeType,ValidationFailed,Unauthorized— four of the sixNodeUpsertRejectionReasonvalues.Unknown(every unclassified infrastructure fault:"Persistence read failed…", "Inner CreateNode faulted…") and
PatchFailed(ambiguous — a contentconflict or a lost race) are excluded on purpose, because getting that backwards is #3101.
Three things it does not cover, all of them now stated rather than assumed:
Preservedis not a failure at all, soIsContentVerdictnever sees it. It is nonethelessdeterministic at a fixed commit — the preserve decision is taken against
lastSyncedAt, which nore-import moves — which is why
VerdictIsFinaljudges it separately rather than deferring to theclassifier.
ValidationFailed, notUnknown— measured and pinnedby
FailedImportIsNotRetriedAtTheSameFingerprintTest.AThrowingValidator_IsRecordedAsAContentVerdict.So a validator whose own dependency is briefly unavailable is recorded as final. That residual is
pre-existing (it is StaticRepoImport: a FAILED import at fingerprint F is re-run in full on every trigger — memex-cloud re-imports the core samples every ~10 min (425 failing upserts + NodeType compiles per pass) #3146's, named in that test's remark), it is inherited here unchanged, and its
fix is in the validator contract.
PruneRefusedis invisible to it — a truncated listing is not a per-node failure — soVerdictIsFinaltests it directly: a run that could not see the whole source has not reached averdict about it.
The cron probe: measured, and deliberately NOT excluded
pushrun. So excluding it removes no signal — and saves nothing the skip above does not already save,
since a converging source skipped those deliveries already and a settled one now does too.
schedule: all sixsatellites' content CI fires on
pushandrepository_dispatchandschedule, and countingshas whose only green content-CI run was a
schedulerun gives zero on or after 2026-09-01.Over the whole of September, dropping
schedulewould have widened exactly one repository's worstquiet stretch, by six hours.
192a60d84a36,push Chart Gateandpush Hosting Operatorboth concludedsuccessin the same second thatpush MeshWeaver Build and Testfailed.🚨 The sweep turned up a correctness defect that is worse than the cost one, and it is filed
separately as #3978:
Auto-update green armed PRs(a*/10cron in three satellites that checksout nothing) recorded 20 green build completions for
MeshWeaver.Reinsurance@7d29a303and 40+for
MeshWeaver.SocialMedia@07cc155e, both commits whose content CI had failed. The triggerevent is a proxy for the wrong proposition; the discriminator that would work is
workflow_run.name/.path— every repository has exactly one content CI, and it fires on all threeadmitted triggers, so keying on it needs no trigger narrowing at all.
Controls
Both end-to-end on the clone itself, because the transfer is the cost:
AContentVerdictAtThisCommit_CostsNoSecondClone_AndANewCommitStillDoesImportAFailureThatMightNotRecur_IsStillAttemptedAtTheSameCommit— the one that could falsify the first.Doc:
Doc/Architecture/GitSyncTriggerCost§7 (the fix) and §8 (the trigger evidence), andGitHubSync's three clocks are now four facts. §4 also names a discrepancy found on the way anddeliberately left alone:
MayAdvanceBaselinedoes not readPruneRefusedwhileStaticRepoImportResult.Convergeddoes, so a truncated-listing run advances the baseline anyway.Closing that would make a source pay more clones, so it is noted rather than changed.
rbuergi commentedon Sep 11, 2026
Completed and verified in production.
f1a945d5).memex.meshweaver.cloud,Essentials/_GitSyncnow carrieslastAttemptedCommitSha: 8ee8a192...andlastAttemptWasFinal: true(version 385, written 2026-09-11 01:10:45Z), while the conservativelastSyncCommitSharemains at the last converged commit as designed.Closed as completed, not won't-fix.