gh-gei icon indicating copy to clipboard operation
gh-gei copied to clipboard

Adding repo to team fails with "Unauthorized. Please check your token and try again" even when the token has repo permissions

Open kine opened this issue 5 months ago • 4 comments

Description

I am using gh ado2gh to test migration of our repos from DevOps to GHE. But the migration script fails with error 401 on step, where the migrated repository is added to created team. I have used generate-script with --sequential to make the migration sequential for purpose of the test.

Reproduction Steps

Used script:

#!/usr/bin/env pwsh

# =========== Created with CLI version 1.17.0 ===========

function Exec {
    param (
        [scriptblock]$ScriptBlock
    )
    & @ScriptBlock
    if ($lastexitcode -ne 0) {
        exit $lastexitcode
    }
}

if (-not $env:ADO_PAT) {
    Write-Error "ADO_PAT environment variable must be set to a valid Azure DevOps Personal Access Token with the appropriate scopes. For more information see https://docs.github.com/en/migrations/using-github-enterprise-importer/preparing-to-migrate-with-github-enterprise-importer/managing-access-for-github-enterprise-importer#personal-access-tokens-for-azure-devops"
    exit 1
}
else {
    Write-Host "ADO_PAT environment variable is set and will be used to authenticate to Azure DevOps."
}

if (-not $env:GH_PAT) {
    Write-Error "GH_PAT environment variable must be set to a valid GitHub Personal Access Token with the appropriate scopes. For more information see https://docs.github.com/en/migrations/using-github-enterprise-importer/preparing-to-migrate-with-github-enterprise-importer/managing-access-for-github-enterprise-importer#creating-a-personal-access-token-for-github-enterprise-importer"
    exit 1
}
else {
    Write-Host "GH_PAT environment variable is set and will be used to authenticate to GitHub."
}
# =========== Organization: XXX===========

# === Team Project: XXX/JJL Test ===
Exec { gh ado2gh create-team --target-api-url "https://api.XXX.ghe.com" --github-org "TestMigration3" --team-name "JJL-Test-Maintainers" }
Exec { gh ado2gh create-team --target-api-url "https://api.XXX.ghe.com" --github-org "TestMigration3" --team-name "JJL-Test-Admins" }

Exec { gh ado2gh migrate-repo --target-api-url "https://api.XXX.ghe.com" --ado-org "XXX" --ado-team-project "JJL Test" --ado-repo "JJL Test" --github-org "TestMigration3" --github-repo "JJL-Test-JJL-Test" --target-repo-visibility private }
Exec { gh ado2gh add-team-to-repo --github-org "TestMigration3" --github-repo "JJL-Test-JJL-Test" --team "JJL-Test-Maintainers" --role "maintain" }
Exec { gh ado2gh add-team-to-repo --github-org "TestMigration3" --github-repo "JJL-Test-JJL-Test" --team "JJL-Test-Admins" --role "admin" }

It fails on the gh ado2gh add-team-to-repo step.

When I try directly the failing command (gh ado2gh add-team-to-repo) with --verbose, I get this output:

gh ado2gh add-team-to-repo --github-org "TestMigration3" --github-repo "JJL-Test-JJL-Test" --team "JJL-Test-Maintainers" --role "maintain" --verbose
[2025-07-28 08:02:30] [INFO] You are running an up-to-date version of the ado2gh CLI [v1.17.0]
[2025-07-28 08:02:30] [INFO] GITHUB ORG: TestMigration3
[2025-07-28 08:02:30] [INFO] GITHUB REPO: JJL-Test-JJL-Test
[2025-07-28 08:02:30] [INFO] TEAM: JJL-Test-Maintainers
[2025-07-28 08:02:30] [INFO] ROLE: maintain
[2025-07-28 08:02:30] [INFO] VERBOSE: true
[2025-07-28 08:02:30] [INFO] Adding team to repo...
[2025-07-28 08:02:30] [DEBUG] HTTP GET: https://api.github.com/orgs/TestMigration3/teams
[2025-07-28 08:02:30] [DEBUG] GITHUB REQUEST ID: DAA0:E89A9:7825072:7C8FE6C:68871276
[2025-07-28 08:02:30] [DEBUG] RESPONSE (Unauthorized): {"message":"Bad credentials","documentation_url":"https://docs.github.com/rest","status":"401"}
[2025-07-28 08:02:30] [DEBUG] [HTTP ERROR 401] System.Net.Http.HttpRequestException: GitHub API error: {"message":"Bad credentials","documentation_url":"https://docs.github.com/rest","status":"401"}
 ---> System.Net.Http.HttpRequestException: Response status code does not indicate success: 401 (Unauthorized).
   at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
   at OctoshiftCLI.Services.GithubClient.SendAsync(HttpMethod httpMethod, String url, Object body, HttpStatusCode expectedStatus, Dictionary`2 customHeaders)
   --- End of inner exception stack trace ---
   at OctoshiftCLI.Services.GithubClient.SendAsync(HttpMethod httpMethod, String url, Object body, HttpStatusCode expectedStatus, Dictionary`2 customHeaders)
   at OctoshiftCLI.Services.GithubClient.<>c__DisplayClass20_0.<<GetWithRetry>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Polly.Retry.AsyncRetryEngine.ImplementationAsync[TResult](Func`3 action, Context context, CancellationToken cancellationToken, ExceptionPredicates shouldRetryExceptionPredicates, ResultPredicates`1 shouldRetryResultPredicates, Func`5 onRetryAsync, Int32 permittedRetryCount, IEnumerable`1 sleepDurationsEnumerable, Func`4 sleepDurationProvider, Boolean continueOnCapturedContext)
[2025-07-28 08:02:30] [ERROR] OctoshiftCLI.OctoshiftCliException: Unauthorized. Please check your token and try again
 ---> System.Net.Http.HttpRequestException: GitHub API error: {"message":"Bad credentials","documentation_url":"https://docs.github.com/rest","status":"401"}
 ---> System.Net.Http.HttpRequestException: Response status code does not indicate success: 401 (Unauthorized).
   at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
   at OctoshiftCLI.Services.GithubClient.SendAsync(HttpMethod httpMethod, String url, Object body, HttpStatusCode expectedStatus, Dictionary`2 customHeaders)
   --- End of inner exception stack trace ---
   at OctoshiftCLI.Services.GithubClient.SendAsync(HttpMethod httpMethod, String url, Object body, HttpStatusCode expectedStatus, Dictionary`2 customHeaders)
   at OctoshiftCLI.Services.GithubClient.<>c__DisplayClass20_0.<<GetWithRetry>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Polly.Retry.AsyncRetryEngine.ImplementationAsync[TResult](Func`3 action, Context context, CancellationToken cancellationToken, ExceptionPredicates shouldRetryExceptionPredicates, ResultPredicates`1 shouldRetryResultPredicates, Func`5 onRetryAsync, Int32 permittedRetryCount, IEnumerable`1 sleepDurationsEnumerable, Func`4 sleepDurationProvider, Boolean continueOnCapturedContext)
   --- End of inner exception stack trace ---
   at OctoshiftCLI.RetryPolicy.<CreateRetryPolicyForException>b__8_1[TException](Exception ex, TimeSpan ts, Context ctx)
   at Polly.AsyncRetrySyntax.<>c__DisplayClass22_0.<<WaitAndRetryAsync>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Polly.Retry.AsyncRetryEngine.ImplementationAsync[TResult](Func`3 action, Context context, CancellationToken cancellationToken, ExceptionPredicates shouldRetryExceptionPredicates, ResultPredicates`1 shouldRetryResultPredicates, Func`5 onRetryAsync, Int32 permittedRetryCount, IEnumerable`1 sleepDurationsEnumerable, Func`4 sleepDurationProvider, Boolean continueOnCapturedContext)
   at Polly.AsyncPolicy.ExecuteAsync[TResult](Func`3 action, Context context, CancellationToken cancellationToken, Boolean continueOnCapturedContext)
   at OctoshiftCLI.RetryPolicy.Retry[T](Func`1 func)
   at OctoshiftCLI.Services.GithubClient.GetWithRetry(String url, Dictionary`2 customHeaders, HttpStatusCode expectedStatus)
   at OctoshiftCLI.Services.GithubClient.GetAllAsync(String url, Func`2 resultCollectionSelector, Dictionary`2 customHeaders)+MoveNext()
   at OctoshiftCLI.Services.GithubClient.GetAllAsync(String url, Func`2 resultCollectionSelector, Dictionary`2 customHeaders)+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult()
   at System.Linq.AsyncEnumerable.<SingleAsync>g__Core|335_0[TSource](IAsyncEnumerable`1 source, Func`2 predicate, CancellationToken cancellationToken) in /_/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Single.cs:line 82
   at System.Linq.AsyncEnumerable.<SingleAsync>g__Core|335_0[TSource](IAsyncEnumerable`1 source, Func`2 predicate, CancellationToken cancellationToken) in /_/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Single.cs:line 82
   at OctoshiftCLI.Services.GithubApi.GetTeamSlug(String org, String teamName)
   at OctoshiftCLI.AdoToGithub.Commands.AddTeamToRepo.AddTeamToRepoCommandHandler.Handle(AddTeamToRepoCommandArgs args)
   at OctoshiftCLI.Extensions.CommandExtensions.RunHandler[TArgs,THandler](TArgs args, ServiceProvider sp, CommandBase`2 command)
   at OctoshiftCLI.Extensions.CommandExtensions.<>c__DisplayClass1_0`3.<<ConfigureCommand>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at System.CommandLine.Invocation.AnonymousCommandHandler.InvokeAsync(InvocationContext)
   at System.CommandLine.Invocation.InvocationPipeline.<>c__DisplayClass4_0.<<BuildInvocationChain>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass17_0.<<UseParseErrorReporting>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass12_0.<<UseHelp>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass22_0.<<UseVersionOption>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass19_0.<<UseTypoCorrections>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c.<<UseSuggestDirective>b__18_0>d.MoveNext()
--- End of stack trace from previous location ---
   at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass16_0.<<UseParseDirective>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c.<<RegisterWithDotnetSuggest>b__5_0>d.MoveNext()
--- End of stack trace from previous location ---
   at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass8_0.<<UseExceptionHandler>b__0>d.MoveNext()

The token has these permissions:

Image

Based on the verbose output, it seems that this command doesn't use the --target-api-url parameter and is calling the api.github.com instead api.xxx.ghe.com. It is needed to extend it in same way as done in #1365

kine avatar Jul 28 '25 06:07 kine

I am checking the code and I do not understand why the AddTeamToRepo function is part of the ado2gh (ado2gh/Commands). I see it as generic tool, which should be on same level as CreateTeam function (which is in Octoshift/Commands).

kine avatar Jul 28 '25 12:07 kine

Your token is going to need the repo scope. The docs give more info.

dylan-smith avatar Jul 29 '25 00:07 dylan-smith

It is there.. check the screenshot again. It is checked, just grayed out. If it is not checked, I will not be able to migrate the repo, but the repo is migrated successfully.

The problem is in line:

[2025-07-28 08:02:30] [DEBUG] HTTP GET: https://api.github.com/orgs/TestMigration3/teams

I am using xxx.ghe.com as target (GitHub with data residency).

kine avatar Jul 29 '25 05:07 kine

I'm seeing the same issue - I did find it strange that selecting the admin:org scope caused the repo scope(s) to become inactive but also checked, I think mostly due to the lack of contrast as it is hard to see that it was still checked

AdamWorley avatar Aug 20 '25 14:08 AdamWorley