@@ -287,7 +287,8 @@ async function listWikis(
287287 accessToken : string ,
288288 organization : string ,
289289 project : string ,
290- retryOptions ?: Parameters < typeof fetchWithRetry > [ 2 ]
290+ retryOptions ?: Parameters < typeof fetchWithRetry > [ 2 ] ,
291+ syncContext ?: Record < string , unknown >
291292) : Promise < WikiV2 [ ] > {
292293 const url = `${ ADO_BASE_URL } /${ encodeURIComponent ( organization ) } /${ encodeURIComponent ( project ) } /_apis/wiki/wikis?api-version=${ WIKIS_LIST_API_VERSION } `
293294 const response = await fetchWithRetry (
@@ -300,6 +301,15 @@ async function listWikis(
300301 )
301302 if ( ! response . ok ) {
302303 if ( response . status === 401 || response . status === 403 || response . status === 404 ) {
304+ /**
305+ * 401/403 mean the wikis still exist but this PAT cannot read them right
306+ * now — flag the listing as incomplete so reconciliation does not delete
307+ * previously synced wiki pages. A 404 means the wiki feature/content is
308+ * genuinely absent, so reconciliation stays enabled.
309+ */
310+ if ( ( response . status === 401 || response . status === 403 ) && syncContext ) {
311+ syncContext . listingCapped = true
312+ }
303313 logger . warn ( 'Azure DevOps wikis unavailable; skipping wiki listing' , {
304314 organization,
305315 project,
@@ -327,7 +337,7 @@ async function resolveWikis(
327337) : Promise < WikiV2 [ ] > {
328338 const cached = syncContext ?. wikis as WikiV2 [ ] | undefined
329339 if ( cached ) return cached
330- const wikis = await listWikis ( accessToken , organization , project )
340+ const wikis = await listWikis ( accessToken , organization , project , undefined , syncContext )
331341 if ( syncContext ) syncContext . wikis = wikis
332342 return wikis
333343}
@@ -642,7 +652,8 @@ async function listRepositories(
642652 accessToken : string ,
643653 organization : string ,
644654 project : string ,
645- retryOptions ?: Parameters < typeof fetchWithRetry > [ 2 ]
655+ retryOptions ?: Parameters < typeof fetchWithRetry > [ 2 ] ,
656+ syncContext ?: Record < string , unknown >
646657) : Promise < GitRepository [ ] > {
647658 const url = `${ ADO_BASE_URL } /${ encodeURIComponent ( organization ) } /${ encodeURIComponent ( project ) } /_apis/git/repositories?api-version=${ GIT_API_VERSION } `
648659 const response = await fetchWithRetry (
@@ -655,6 +666,15 @@ async function listRepositories(
655666 )
656667 if ( ! response . ok ) {
657668 if ( response . status === 401 || response . status === 403 || response . status === 404 ) {
669+ /**
670+ * 401/403 mean repositories still exist but this PAT cannot read them
671+ * right now — flag the listing as incomplete so reconciliation does not
672+ * delete previously synced repository files. A 404 means the Git feature
673+ * is genuinely absent, so reconciliation stays enabled.
674+ */
675+ if ( ( response . status === 401 || response . status === 403 ) && syncContext ) {
676+ syncContext . listingCapped = true
677+ }
658678 logger . warn ( 'Azure DevOps repositories unavailable; skipping file listing' , {
659679 organization,
660680 project,
@@ -686,7 +706,8 @@ async function resolveRepositories(
686706 syncContext ?: Record < string , unknown >
687707) : Promise < GitRepository [ ] > {
688708 const cached = syncContext ?. repositories as GitRepository [ ] | undefined
689- const all = cached ?? ( await listRepositories ( accessToken , organization , project ) )
709+ const all =
710+ cached ?? ( await listRepositories ( accessToken , organization , project , undefined , syncContext ) )
690711 if ( syncContext && ! cached ) syncContext . repositories = all
691712
692713 const needle = repositoryFilter . toLowerCase ( )
@@ -707,7 +728,8 @@ async function listRepositoryBlobs(
707728 organization : string ,
708729 project : string ,
709730 repoId : string ,
710- branch : string
731+ branch : string ,
732+ syncContext ?: Record < string , unknown >
711733) : Promise < GitItem [ ] > {
712734 const params = new URLSearchParams ( {
713735 recursionLevel : 'Full' ,
@@ -722,6 +744,16 @@ async function listRepositoryBlobs(
722744 } )
723745 if ( ! response . ok ) {
724746 if ( response . status === 401 || response . status === 403 || response . status === 404 ) {
747+ /**
748+ * 401/403 mean the repository's files still exist but this PAT cannot
749+ * read them right now — flag the listing as incomplete so reconciliation
750+ * does not delete previously synced files. A 404 means the branch/repo
751+ * content is genuinely absent (empty repo, deleted branch), so
752+ * reconciliation stays enabled.
753+ */
754+ if ( ( response . status === 401 || response . status === 403 ) && syncContext ) {
755+ syncContext . listingCapped = true
756+ }
725757 logger . warn ( 'Azure DevOps repository items unavailable; skipping repository' , {
726758 repoId,
727759 branch,
@@ -824,7 +856,14 @@ async function resolveRepoFiles(
824856 } )
825857 continue
826858 }
827- const blobs = await listRepositoryBlobs ( accessToken , organization , project , repo . id , branch )
859+ const blobs = await listRepositoryBlobs (
860+ accessToken ,
861+ organization ,
862+ project ,
863+ repo . id ,
864+ branch ,
865+ syncContext
866+ )
828867 for ( const item of blobs ) {
829868 if ( normalizedPrefix && ! item . path . startsWith ( normalizedPrefix ) ) continue
830869 if ( ! matchesExtension ( item . path , filters . extensions ) ) continue
0 commit comments