-
Notifications
You must be signed in to change notification settings - Fork 0
fix(mount): ignore stale GitHub head records in strict bootstrap #513
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7584,6 +7584,11 @@ func (s *Syncer) pullRemoteFullTree(ctx context.Context, conflicted map[string]s | |
| } | ||
| continue | ||
| } | ||
| if strictCompleteGithubSource && | ||
| (page.Entries[i].Type == remoteTypeFile || page.Entries[i].Type == remoteTypeSymlink) && | ||
| s.githubWorkingTreeRemotePathHasStaleHead(page.Entries[i].Path) { | ||
| continue | ||
| } | ||
| if remainingFileBudget >= 0 && fileEntriesThisChunk >= remainingFileBudget { | ||
| entryEnd = i | ||
| break | ||
|
|
@@ -7618,6 +7623,11 @@ func (s *Syncer) pullRemoteFullTree(ctx context.Context, conflicted map[string]s | |
| if !isUnderRemoteRoot(s.remoteRoot, remotePath) { | ||
| continue | ||
| } | ||
| if strictCompleteGithubSource && | ||
| (entry.Type == remoteTypeFile || entry.Type == remoteTypeSymlink) && | ||
| s.githubWorkingTreeRemotePathHasStaleHead(remotePath) { | ||
| continue | ||
|
Comment on lines
+7626
to
+7629
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Multiple stale heads stall bootstrap With more stale tracked records than current-head files, Learn moreThe strict traversal omits stale-head records from the fresh snapshot but leaves existing stale records in Example: A failed earlier bootstrap tracked 100 current records and 200 records from two older heads. The fixed traversal produces 100 Recommended fix: Exclude records identified by Was this helpful? React with 👍 or 👎 to provide feedback.
Comment on lines
+7626
to
+7629
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stale skip deletes live filesHigh Severity Skipping stale revision-suffixed records leaves previously tracked Additional Locations (1)Reviewed by Cursor Bugbot for commit cef3c2a. Configure here.
Comment on lines
+7626
to
+7630
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🤖 get_repo_knowledge executed:
Length of output: 2805 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- relevant files ---'
git ls-files | rg '(^|/)(syncer\.go|.*mountsync.*|.*sync.*test.*)$' | head -80
printf '%s\n' '--- symbols and references ---'
rg -n -C 5 'applyRemoteSnapshotDeletesRev|remotePaths|githubWorkingTreeRemotePathHasStaleHead|SnapshotDeletes|decoded local path|localPath' internal/mountsync/syncer.go internal/mountsync 2>/dev/null | head -500Repository: AgentWorkforce/relayfile Length of output: 35622 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- snapshot delete definitions and callers ---'
rg -n -C 12 'func \(s \*Syncer\) applyRemoteSnapshotDeletesRev|applyRemoteSnapshotDeletesRev\(|applyRemoteSnapshotDeletes|decode.*Remote|remotePath.*local|safeLocalPath' internal/mountsync/syncer.go internal/mountsync/*.go
printf '%s\n' '--- exact GitHub strict seed and snapshot-delete ranges ---'
sed -n '6650,6825p' internal/mountsync/syncer.go
printf '%s\n' '--- tests mentioning snapshot delete/bootstrap/stale ---'
rg -n -C 8 'snapshot.*delete|Snapshot.*Delete|stale.*head|stale.*record|bootstrap.*stale|previous.*bootstrap|remotePaths' internal/mountsync/*_test.goRepository: AgentWorkforce/relayfile Length of output: 50381 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- exact function locations ---'
rg -n '^func .*applyRemoteSnapshotDeletes|^func .*removeLocal|^func .*githubWorkingTreeRemotePathHasStaleHead|^func .*applyGithubWorkingTreeTarSeedStrict' internal/mountsync/syncer.go
printf '%s\n' '--- snapshot delete body ---'
line=$(rg -n '^func .*applyRemoteSnapshotDeletesRev' internal/mountsync/syncer.go | cut -d: -f1)
end=$((line+180))
sed -n "${line},${end}p" internal/mountsync/syncer.go
printf '%s\n' '--- strict tar body around stale filtering ---'
sed -n '7550,7660p' internal/mountsync/syncer.go
printf '%s\n' '--- stale-head helper ---'
sed -n '12115,12155p' internal/mountsync/syncer.goRepository: AgentWorkforce/relayfile Length of output: 12473 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- applyRemoteDelete ---'
line=$(rg -n '^func \(s \*Syncer\) applyRemoteDelete' internal/mountsync/syncer.go | cut -d: -f1)
sed -n "${line},$((line+150))p" internal/mountsync/syncer.go
printf '%s\n' '--- tracked path and local removal helpers ---'
rg -n '^func \(s \*Syncer\) trackedRemotePathForLocalPath|^func .*removeLocalNoFollow|^func .*remoteToLocalPath|^func .*applyRemoteFile' internal/mountsync/syncer.go
printf '%s\n' '--- overlap/stale references in tests ---'
rg -n -C 12 'githubWorkingTreeRemotePathHasStaleHead|@.*\\.json|workingTree.*Path|LocalRelativePath|applyRemoteDelete|snapshot delete' internal/mountsync/syncer_test.go internal/mountsync/*_test.go | rg -n 'stale|head|LocalRelativePath|applyRemoteDelete|snapshot|workingTree|@' | head -300Repository: AgentWorkforce/relayfile Length of output: 11636 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- applyRemoteFile body ---'
sed -n '9568,9735p' internal/mountsync/syncer.go
printf '%s\n' '--- working-tree mapping definitions ---'
rg -n -C 8 'func .*remotePathToWorkingTreeRel|func .*workingTreeRelToRemotePath|type .*githubWorkingTree|HeadSHA' internal/mountsync
printf '%s\n' '--- bootstrap stale-related test sections ---'
sed -n '620,720p' internal/mountsync/bootstrap_test.go
sed -n '1530,1610p' internal/mountsync/bootstrap_test.go
printf '%s\n' '--- strict source test names ---'
rg -n '^func Test.*(Github|GitHub|WorkingTree|Bootstrap|Stale|Snapshot)' internal/mountsync/*_test.go | tail -120Repository: AgentWorkforce/relayfile Length of output: 50380 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- remote path conversion and ownership ---'
sed -n '11785,11920p' internal/mountsync/syncer.go
sed -n '11965,12015p' internal/mountsync/syncer.go
printf '%s\n' '--- applyRemoteFile state assignment tail ---'
sed -n '9735,9825p' internal/mountsync/syncer.go
printf '%s\n' '--- current-head mapping test ---'
sed -n '3374,3465p' internal/mountsync/syncer_test.goRepository: AgentWorkforce/relayfile Length of output: 14268 Preserve the local path when deleting a stale GitHub record.
🤖 Prompt for AI Agents |
||
| if strictCompleteGithubSource && (entry.Type == remoteTypeFile || entry.Type == remoteTypeSymlink) && runtimeRoot == "" { | ||
| strictFilesThisChunk++ | ||
| } | ||
|
|
@@ -12121,6 +12131,24 @@ func (s *Syncer) githubWorkingTreeRemotePathMatchesHead(remotePath string) bool | |
| return strings.HasSuffix(normalizeRemotePath(remotePath), "@"+headSHA+".json") | ||
| } | ||
|
|
||
| func (s *Syncer) githubWorkingTreeRemotePathHasStaleHead(remotePath string) bool { | ||
| if s.githubWorkingTree == nil { | ||
| return false | ||
| } | ||
| headSHA := strings.TrimSpace(s.githubWorkingTree.HeadSHA) | ||
| if headSHA == "" { | ||
| return false | ||
| } | ||
| normalized := normalizeRemotePath(remotePath) | ||
| if strings.HasSuffix(normalized, "@"+headSHA+".json") { | ||
| return false | ||
| } | ||
| if _, ok := s.githubWorkingTree.remotePathToWorkingTreeRel(normalized); !ok { | ||
| return false | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| func safeLocalPath(localRoot, rel string) (string, error) { | ||
| localRoot = filepath.Clean(localRoot) | ||
| rel = filepath.ToSlash(strings.TrimSpace(rel)) | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 Head changes corrupt resumed verification
When
HeadSHAchanges during resumed bootstrap,githubWorkingTreeRemotePathHasStaleHeadswitches filtering without resetting the checkpoint.BootstrapStrictFilesSeenretains old-head files, while the persisted cursor can skip earlier new-head records. The mixed count can complete an incomplete mount.Learn more
A strict checkpoint consists of a tree cursor, page offset, directory frontier, and the number of accepted files already traversed. The manifest loader can replace
githubWorkingTree.HeadSHAandGithubWorkingTreeFilesExpectedon each tar-seed attempt at pullRemoteFullGithubTarSeed. The new filter then evaluates the remaining traversal against the new head, but checkpoint restoration restores the old count and cursor unchanged. If the server keeps that cursor valid, new-head records located before it are never visited, while old-head records already counted remain in the strict total.Example: Head A processes two of four files and persists a cursor. Before the next cycle, head B also declares four files and inserts two B records before that cursor. The resumed cycle keeps A's count of two, counts two B records after the cursor, reaches four, and marks bootstrap complete without materializing the two earlier B records.
Recommended fix: Detect a manifest head change before overwriting
GithubWorkingTreeHeadSHA. For an incomplete bootstrap, invalidate the entire bounded-tree checkpoint and strict counters so traversal restarts from the root under the new head and denominator. Persist the reset before falling back topullRemoteFullTree.Was this helpful? React with 👍 or 👎 to provide feedback.