@@ -113,12 +113,18 @@ const (
113113 defaultCursorResolutionAttempts = 3
114114 defaultCursorRetryBaseDelay = 250 * time .Millisecond
115115 defaultBootstrapReadWorkers = 16
116- defaultIncrementalEventPageLimit = 50
117- defaultRetryAfterMaxDelay = 60 * time .Second
118- defaultWebSocketReconnectBase = 1 * time .Second
119- defaultWebSocketReconnectMax = 60 * time .Second
120- defaultWebSocketReconnectJitter = 200 * time .Millisecond
121- DefaultWebSocketMaintenanceEvery = 1 * time .Second
116+ // fullTreeTraversalDepth bounds each tree request so the client can see and
117+ // prune a reserved .relay directory before the server reaches deep
118+ // outbox/acked/mountcmd_* leaves. Depth 3 also covers the common Slack
119+ // channels/<id>/messages frontier in one request, avoiding one network
120+ // round trip per message directory.
121+ fullTreeTraversalDepth = 3
122+ defaultIncrementalEventPageLimit = 50
123+ defaultRetryAfterMaxDelay = 60 * time .Second
124+ defaultWebSocketReconnectBase = 1 * time .Second
125+ defaultWebSocketReconnectMax = 60 * time .Second
126+ defaultWebSocketReconnectJitter = 200 * time .Millisecond
127+ DefaultWebSocketMaintenanceEvery = 1 * time .Second
122128)
123129
124130// resolveDurationEnv returns the option value if non-zero, else parses the
@@ -4047,10 +4053,11 @@ func (s *Syncer) pullRemoteFullTree(ctx context.Context, conflicted map[string]s
40474053 )
40484054 }()
40494055 remotePaths := map [string ]struct {}{}
4050- // Traverse one directory level at a time . A deep ListTree request makes the
4056+ // Traverse in bounded-depth chunks . A depth=200 ListTree request makes the
40514057 // server enumerate every descendant before the client can reject reserved
4052- // runtime paths. The shallow queue sees a nested .relay directory first and
4053- // can prune it without ever requesting its state/outbox descendants.
4058+ // runtime paths. A small fixed depth sees a nested .relay directory before
4059+ // it reaches deep outbox/acked/mountcmd_* leaves, while advancing through a
4060+ // representative Slack tree in a few requests rather than one per directory.
40544061 directories := []string {s .remoteRoot }
40554062 cursor := ""
40564063 startedFromEmpty := true
@@ -4067,13 +4074,13 @@ func (s *Syncer) pullRemoteFullTree(ctx context.Context, conflicted map[string]s
40674074 directories = resumedDirectories
40684075 cursor = strings .TrimSpace (s .state .BootstrapCursor )
40694076 startedFromEmpty = false
4070- s .logf ("resuming bootstrap shallow -tree pull at %s from persisted cursor (%d directories pending, %d files already synced)" , directories [0 ], len (directories ), s .state .BootstrapFilesSynced )
4077+ s .logf ("resuming bootstrap bounded -tree pull at %s from persisted cursor (%d directories pending, %d files already synced)" , directories [0 ], len (directories ), s .state .BootstrapFilesSynced )
40714078 }
40724079 } else if ! s .state .BootstrapComplete && strings .TrimSpace (s .state .BootstrapCursor ) != "" {
40734080 // v0.10.28 and older persisted a cursor into one depth=200 listing.
4074- // That cursor is invalid for a depth=1 directory page, so restart from
4081+ // That cursor is invalid for a bounded- depth directory page, so restart from
40754082 // the root. The local-hash fast path avoids re-reading unchanged files.
4076- s .logf ("discarding legacy deep-tree bootstrap cursor and restarting shallow traversal from %s" , s .remoteRoot )
4083+ s .logf ("discarding legacy deep-tree bootstrap cursor and restarting bounded traversal from %s" , s .remoteRoot )
40774084 s .state .BootstrapCursor = ""
40784085 }
40794086 queuedDirectories := make (map [string ]struct {}, len (directories ))
@@ -4099,12 +4106,13 @@ func (s *Syncer) pullRemoteFullTree(ctx context.Context, conflicted map[string]s
40994106 }
41004107 maxObservedRevision := ""
41014108 var transientBootstrapAbort bool
4109+ prunedRuntimeRoots := map [string ]struct {}{}
41024110 for len (directories ) > 0 {
41034111 currentDirectory := directories [0 ]
41044112 var page TreeResponse
41054113 var err error
41064114 s .runFullPullIO (func () {
4107- page , err = s .client .ListTree (ctx , s .workspace , currentDirectory , 1 , cursor )
4115+ page , err = s .client .ListTree (ctx , s .workspace , currentDirectory , fullTreeTraversalDepth , cursor )
41084116 })
41094117 metrics .listCalls ++
41104118 if err != nil {
@@ -4127,15 +4135,18 @@ func (s *Syncer) pullRemoteFullTree(ctx context.Context, conflicted map[string]s
41274135 case "dir" :
41284136 metrics .directoriesSeen ++
41294137 }
4130- if isMountRuntimeRemotePath (remotePath ) {
4138+ runtimeRoot := mountRuntimeRemoteRoot (remotePath )
4139+ if runtimeRoot != "" {
41314140 metrics .runtimeEntriesSeen ++
41324141 }
41334142 if ! isUnderRemoteRoot (s .remoteRoot , remotePath ) {
41344143 continue
41354144 }
4136- if s .skipMountRuntimeRemotePath (remotePath , conflicted ) {
4137- if entry .Type == "dir" {
4145+ if runtimeRoot != "" {
4146+ if _ , pruned := prunedRuntimeRoots [runtimeRoot ]; ! pruned {
4147+ prunedRuntimeRoots [runtimeRoot ] = struct {}{}
41384148 metrics .runtimeSubtreesPruned ++
4149+ s .skipMountRuntimeRemotePath (runtimeRoot , conflicted )
41394150 }
41404151 continue
41414152 }
@@ -4144,7 +4155,10 @@ func (s *Syncer) pullRemoteFullTree(ctx context.Context, conflicted map[string]s
41444155 continue
41454156 }
41464157 if entry .Type == "dir" {
4147- if _ , exists := queuedDirectories [remotePath ]; ! exists {
4158+ if remoteDescendantDepth (currentDirectory , remotePath ) >= fullTreeTraversalDepth {
4159+ if _ , exists := queuedDirectories [remotePath ]; exists {
4160+ continue
4161+ }
41484162 queuedDirectories [remotePath ] = struct {}{}
41494163 directories = append (directories , remotePath )
41504164 }
@@ -5539,8 +5553,8 @@ func (s *Syncer) applyRemoteFile(remotePath string, file RemoteFile, conflicted
55395553}
55405554
55415555func (s * Syncer ) skipMountRuntimeRemotePath (remotePath string , conflicted map [string ]struct {}) bool {
5542- remotePath = normalizeRemotePath (remotePath )
5543- if ! isMountRuntimeRemotePath ( remotePath ) {
5556+ remotePath = mountRuntimeRemoteRoot (remotePath )
5557+ if remotePath == "" {
55445558 return false
55455559 }
55465560 s .logf ("skipping mount runtime path surfaced as workspace content: %s" , remotePath )
@@ -5586,8 +5600,9 @@ func (s *Syncer) isActiveMountRuntimeLocalPath(localPath string) bool {
55865600 if localPath == stateFile {
55875601 return true
55885602 }
5603+ stateTempPrefix := strings .TrimSuffix (atomicTempPattern (stateFile ), "*" )
55895604 return filepath .Dir (localPath ) == filepath .Dir (stateFile ) &&
5590- strings .HasPrefix (filepath .Base (localPath ), "." + filepath . Base ( stateFile ) + ".tmp-" )
5605+ strings .HasPrefix (filepath .Base (localPath ), stateTempPrefix )
55915606}
55925607
55935608func (s * Syncer ) applyLocalPermissions (localPath string , canWrite bool ) error {
@@ -6223,25 +6238,34 @@ func normalizeScopes(scopes []string) []string {
62236238}
62246239
62256240func isMountRuntimeRemotePath (path string ) bool {
6226- return isMountRuntimeRelativePath ( strings . TrimPrefix ( normalizeRemotePath ( path ), "/" ))
6241+ return mountRuntimeRemoteRoot ( path ) != ""
62276242}
62286243
62296244func isMountRuntimeRelativePath (path string ) bool {
6230- normalized := filepath .ToSlash (strings .TrimSpace (path ))
6245+ return mountRuntimeRemoteRoot ("/" + strings .TrimPrefix (filepath .ToSlash (strings .TrimSpace (path )), "/" )) != ""
6246+ }
6247+
6248+ // mountRuntimeRemoteRoot returns the reserved runtime subtree containing path.
6249+ // Canonicalizing descendants to their first reserved segment lets a bounded
6250+ // tree page clean/log one .relay root without treating its state/outbox
6251+ // children as separate subtrees.
6252+ func mountRuntimeRemoteRoot (path string ) string {
6253+ normalized := strings .TrimPrefix (filepath .ToSlash (normalizeRemotePath (path )), "/" )
62316254 if normalized == "" || normalized == "." {
6232- return false
6255+ return ""
62336256 }
6234- for _ , segment := range strings .Split (normalized , "/" ) {
6257+ segments := strings .Split (normalized , "/" )
6258+ for index , segment := range segments {
62356259 if segment == "" || segment == "." {
62366260 continue
62376261 }
62386262 if segment == ".relay" ||
62396263 segment == ".relayfile-mount-state.json" ||
62406264 strings .HasPrefix (segment , ".relayfile-mount-state.json.tmp-" ) {
6241- return true
6265+ return normalizeRemotePath ( "/" + strings . Join ( segments [: index + 1 ], "/" ))
62426266 }
62436267 }
6244- return false
6268+ return ""
62456269}
62466270
62476271func (s * Syncer ) logMountControlPathSkipped (relativePath string ) {
@@ -6851,6 +6875,20 @@ func isUnderRemoteRoot(remoteRoot, remotePath string) bool {
68516875 return remotePath == remoteRoot || strings .HasPrefix (remotePath , remoteRoot + "/" )
68526876}
68536877
6878+ func remoteDescendantDepth (remoteRoot , remotePath string ) int {
6879+ remoteRoot = normalizeRemotePath (remoteRoot )
6880+ remotePath = normalizeRemotePath (remotePath )
6881+ if remotePath == remoteRoot || ! isUnderRemoteRoot (remoteRoot , remotePath ) {
6882+ return 0
6883+ }
6884+ relative := strings .TrimPrefix (remotePath , remoteRoot )
6885+ relative = strings .TrimPrefix (relative , "/" )
6886+ if relative == "" {
6887+ return 0
6888+ }
6889+ return len (strings .Split (relative , "/" ))
6890+ }
6891+
68546892func remoteToLocalPath (localRoot , remoteRoot , remotePath string ) (string , error ) {
68556893 localRoot = filepath .Clean (localRoot )
68566894 remoteRoot = normalizeRemotePath (remoteRoot )
0 commit comments