Repository: cli/cli

Issue:
`gh repo sync` fails with surprising error if `workflow` scope is missing and token is from GitHub App
### Describe the bug

This is an extension of https://github.com/cli/cli/issues/7574 based on the comment that the [previous fix](https://github.com/cli/cli/pull/7612) doesn't work for [GitHub App tokens.](https://github.com/cli/cli/issues/7574#issuecomment-2709551166)

### Affected version

`v2.68.1`

### Acceptance Criteria

**Given** I have a GitHub App token that is missing `workflow` scope
**When** I run `gh repo sync` where the upstream repo has `workflow` changes to be synced
**Then** I get an informative error rather than a 404.

Hints from issue comments:
@wata727: Thank you for opening the issue.

The easiest way to reproduce this is to run `gh repo sync <remote>` using the token issued by GitHub Actions. Please also refer to https://github.com/terraform-linters/magic-modules/pull/7.

The build log is as follows:
https://github.com/terraform-linters/magic-modules/actions/runs/13742985933/job/38434464702

The build log with `GH_DEBUG=api` is as follows:
https://github.com/terraform-linters/magic-modules/actions/runs/13745172118/job/38439197950

Solution patch:
diff --git a/pkg/cmd/repo/sync/http.go b/pkg/cmd/repo/sync/http.go
index 3e193daeb..27e9a6351 100644
--- a/pkg/cmd/repo/sync/http.go
+++ b/pkg/cmd/repo/sync/http.go
@@ -32,8 +32,8 @@ func latestCommit(client *api.Client, repo ghrepo.Interface, branch string) (com
 
 type upstreamMergeErr struct{ error }
 
-var missingWorkflowScopeRE = regexp.MustCompile("refusing to allow.*without `workflow` scope")
-var missingWorkflowScopeErr = errors.New("Upstream commits contain workflow changes, which require the `workflow` scope to merge. To request it, run: gh auth refresh -s workflow")
+var missingWorkflowScopeRE = regexp.MustCompile("refusing to allow.*without `workflow(s)?` (scope|permission)")
+var missingWorkflowScopeErr = errors.New("Upstream commits contain workflow changes, which require the `workflow` scope or permission to merge. To request it, run: gh auth refresh -s workflow")
 
 func triggerUpstreamMerge(client *api.Client, repo ghrepo.Interface, branch string) (string, error) {
 	var payload bytes.Buffer



Test patch:
diff --git a/pkg/cmd/repo/sync/sync_test.go b/pkg/cmd/repo/sync/sync_test.go
index 227e2a6a2..60e6ae392 100644
--- a/pkg/cmd/repo/sync/sync_test.go
+++ b/pkg/cmd/repo/sync/sync_test.go
@@ -470,7 +470,7 @@ func Test_SyncRun(t *testing.T) {
 			errMsg:  "trunk branch does not exist on OWNER/REPO-FORK repository",
 		},
 		{
-			name: "sync remote fork with missing workflow scope on token",
+			name: "sync remote fork with missing workflow scope on OAuth App token",
 			opts: &SyncOptions{
 				DestArg: "FORKOWNER/REPO-FORK",
 			},
@@ -485,7 +485,43 @@ func Test_SyncRun(t *testing.T) {
 					}{Message: "refusing to allow an OAuth App to create or update workflow `.github/workflows/unimportant.yml` without `workflow` scope"}))
 			},
 			wantErr: true,
-			errMsg:  "Upstream commits contain workflow changes, which require the `workflow` scope to merge. To request it, run: gh auth refresh -s workflow",
+			errMsg:  "Upstream commits contain workflow changes, which require the `workflow` scope or permission to merge. To request it, run: gh auth refresh -s workflow",
+		},
+		{
+			name: "sync remote fork with missing workflow permission on GitHub App token",
+			opts: &SyncOptions{
+				DestArg: "FORKOWNER/REPO-FORK",
+			},
+			httpStubs: func(reg *httpmock.Registry) {
+				reg.Register(
+					httpmock.GraphQL(`query RepositoryInfo\b`),
+					httpmock.StringResponse(`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}`))
+				reg.Register(
+					httpmock.REST("POST", "repos/FORKOWNER/REPO-FORK/merge-upstream"),
+					httpmock.StatusJSONResponse(422, struct {
+						Message string `json:"message"`
+					}{Message: "refusing to allow a GitHub App to create or update workflow `.github/workflows/unimportant.yml` without `workflows` permission"}))
+			},
+			wantErr: true,
+			errMsg:  "Upstream commits contain workflow changes, which require the `workflow` scope or permission to merge. To request it, run: gh auth refresh -s workflow",
+		},
+		{
+			name: "sync remote fork with missing workflow scope on personal access token",
+			opts: &SyncOptions{
+				DestArg: "FORKOWNER/REPO-FORK",
+			},
+			httpStubs: func(reg *httpmock.Registry) {
+				reg.Register(
+					httpmock.GraphQL(`query RepositoryInfo\b`),
+					httpmock.StringResponse(`{"data":{"repository":{"defaultBranchRef":{"name": "trunk"}}}}`))
+				reg.Register(
+					httpmock.REST("POST", "repos/FORKOWNER/REPO-FORK/merge-upstream"),
+					httpmock.StatusJSONResponse(422, struct {
+						Message string `json:"message"`
+					}{Message: "refusing to allow a Personal Access Token to create or update workflow `.github/workflows/unimportant.yml` without `workflow` scope"}))
+			},
+			wantErr: true,
+			errMsg:  "Upstream commits contain workflow changes, which require the `workflow` scope or permission to merge. To request it, run: gh auth refresh -s workflow",
 		},
 	}
 	for _, tt := range tests {