Repository: cli/cli
Pull request #10819 (2025-04-17 19:22:18)

Feature detect v1 projects on issue edit
## Description

Relates to: https://github.com/cli/cli/issues/10714
Builds on: https://github.com/cli/cli/pull/10818

This PR tackles projectv1 deprecation on issue editing.

### Reviewer Notes

**DO NOT MERGE** into base branch, wait for base branch to be merged into trunk.

You can verify the presence or absence of `projects` by running:

```
 GH_DEBUG=api ./bin/gh issue edit --repo cli/cli 1000 --add-project "non-existent" 2>&1 | grep "projectCards"
```


Patch:
diff --git a/go.mod b/go.mod
index 31b07f2cf..8fe69cc84 100644
--- a/go.mod
+++ b/go.mod
@@ -53,7 +53,7 @@ require (
 	golang.org/x/sync v0.13.0
 	golang.org/x/term v0.31.0
 	golang.org/x/text v0.24.0
-	google.golang.org/grpc v1.71.1
+	google.golang.org/grpc v1.71.0
 	google.golang.org/protobuf v1.36.6
 	gopkg.in/h2non/gock.v1 v1.1.2
 	gopkg.in/yaml.v3 v3.0.1
diff --git a/go.sum b/go.sum
index b312bcf6c..63a1cdd77 100644
--- a/go.sum
+++ b/go.sum
@@ -601,8 +601,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:
 google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
-google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
-google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
+google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
+google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
 google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
 google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/internal/config/config.go b/internal/config/config.go
index c78dac15c..e7534dfdb 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -14,18 +14,15 @@ import (
 	ghConfig "github.com/cli/go-gh/v2/pkg/config"
 )
 
-// Important: some of the following configuration settings are used outside of `cli/cli`,
-// they are defined here to avoid `cli/cli` being changed unexpectedly.
 const (
-	accessibleColorsKey   = "accessible_colors" // used by cli/go-gh to enable the use of customizable, accessible 4-bit colors.
 	aliasesKey            = "aliases"
-	browserKey            = "browser" // used by cli/go-gh to open URLs in web browsers
+	browserKey            = "browser"
 	colorLabelsKey        = "color_labels"
-	editorKey             = "editor" // used by cli/go-gh to open interactive text editor
+	editorKey             = "editor"
 	gitProtocolKey        = "git_protocol"
-	hostsKey              = "hosts" // used by cli/go-gh to locate authenticated host tokens
+	hostsKey              = "hosts"
 	httpUnixSocketKey     = "http_unix_socket"
-	oauthTokenKey         = "oauth_token" // used by cli/go-gh to locate authenticated host tokens
+	oauthTokenKey         = "oauth_token"
 	pagerKey              = "pager"
 	promptKey             = "prompt"
 	preferEditorPromptKey = "prefer_editor_prompt"
@@ -112,11 +109,6 @@ func (c *cfg) Authentication() gh.AuthConfig {
 	return &AuthConfig{cfg: c.cfg}
 }
 
-func (c *cfg) AccessibleColors(hostname string) gh.ConfigEntry {
-	// Intentionally panic if there is no user provided value or default value (which would be a programmer error)
-	return c.GetOrDefault(hostname, accessibleColorsKey).Unwrap()
-}
-
 func (c *cfg) Browser(hostname string) gh.ConfigEntry {
 	// Intentionally panic if there is no user provided value or default value (which would be a programmer error)
 	return c.GetOrDefault(hostname, browserKey).Unwrap()
@@ -548,8 +540,6 @@ http_unix_socket:
 browser:
 # Whether to display labels using their RGB hex color codes in terminals that support truecolor. Supported values: enabled, disabled
 color_labels: disabled
-# Whether customizable, 4-bit accessible colors should be used. Supported values: enabled, disabled
-accessible_colors: disabled
 `
 
 type ConfigOption struct {
@@ -629,15 +619,6 @@ var Options = []ConfigOption{
 			return c.ColorLabels(hostname).Value
 		},
 	},
-	{
-		Key:           accessibleColorsKey,
-		Description:   "whether customizable, 4-bit accessible colors should be used",
-		DefaultValue:  "disabled",
-		AllowedValues: []string{"enabled", "disabled"},
-		CurrentValue: func(c gh.Config, hostname string) string {
-			return c.AccessibleColors(hostname).Value
-		},
-	},
 }
 
 func HomeDirPath(subdir string) (string, error) {
diff --git a/internal/config/stub.go b/internal/config/stub.go
index 8b9f14290..78073da4a 100644
--- a/internal/config/stub.go
+++ b/internal/config/stub.go
@@ -52,9 +52,6 @@ func NewFromString(cfgStr string) *ghmock.ConfigMock {
 			},
 		}
 	}
-	mock.AccessibleColorsFunc = func(hostname string) gh.ConfigEntry {
-		return cfg.AccessibleColors(hostname)
-	}
 	mock.BrowserFunc = func(hostname string) gh.ConfigEntry {
 		return cfg.Browser(hostname)
 	}
diff --git a/internal/gh/gh.go b/internal/gh/gh.go
index 8f3e3cd5b..b17c6bd67 100644
--- a/internal/gh/gh.go
+++ b/internal/gh/gh.go
@@ -35,8 +35,6 @@ type Config interface {
 	// Set provides primitive access for setting configuration values, optionally scoped by host.
 	Set(hostname string, key string, value string)
 
-	// AccessibleColors returns the configured accessible_colors setting, optionally scoped by host.
-	AccessibleColors(hostname string) ConfigEntry
 	// Browser returns the configured browser, optionally scoped by host.
 	Browser(hostname string) ConfigEntry
 	// ColorLabels returns the configured color_label setting, optionally scoped by host.
diff --git a/internal/gh/mock/config.go b/internal/gh/mock/config.go
index 600eea5c1..b94cb084d 100644
--- a/internal/gh/mock/config.go
+++ b/internal/gh/mock/config.go
@@ -19,9 +19,6 @@ var _ gh.Config = &ConfigMock{}
 //
 //		// make and configure a mocked gh.Config
 //		mockedConfig := &ConfigMock{
-//			AccessibleColorsFunc: func(hostname string) gh.ConfigEntry {
-//				panic("mock out the AccessibleColors method")
-//			},
 //			AliasesFunc: func() gh.AliasConfig {
 //				panic("mock out the Aliases method")
 //			},
@@ -77,9 +74,6 @@ var _ gh.Config = &ConfigMock{}
 //
 //	}
 type ConfigMock struct {
-	// AccessibleColorsFunc mocks the AccessibleColors method.
-	AccessibleColorsFunc func(hostname string) gh.ConfigEntry
-
 	// AliasesFunc mocks the Aliases method.
 	AliasesFunc func() gh.AliasConfig
 
@@ -130,11 +124,6 @@ type ConfigMock struct {
 
 	// calls tracks calls to the methods.
 	calls struct {
-		// AccessibleColors holds details about calls to the AccessibleColors method.
-		AccessibleColors []struct {
-			// Hostname is the hostname argument value.
-			Hostname string
-		}
 		// Aliases holds details about calls to the Aliases method.
 		Aliases []struct {
 		}
@@ -212,7 +201,6 @@ type ConfigMock struct {
 		Write []struct {
 		}
 	}
-	lockAccessibleColors   sync.RWMutex
 	lockAliases            sync.RWMutex
 	lockAuthentication     sync.RWMutex
 	lockBrowser            sync.RWMutex
@@ -231,38 +219,6 @@ type ConfigMock struct {
 	lockWrite              sync.RWMutex
 }
 
-// AccessibleColors calls AccessibleColorsFunc.
-func (mock *ConfigMock) AccessibleColors(hostname string) gh.ConfigEntry {
-	if mock.AccessibleColorsFunc == nil {
-		panic("ConfigMock.AccessibleColorsFunc: method is nil but Config.AccessibleColors was just called")
-	}
-	callInfo := struct {
-		Hostname string
-	}{
-		Hostname: hostname,
-	}
-	mock.lockAccessibleColors.Lock()
-	mock.calls.AccessibleColors = append(mock.calls.AccessibleColors, callInfo)
-	mock.lockAccessibleColors.Unlock()
-	return mock.AccessibleColorsFunc(hostname)
-}
-
-// AccessibleColorsCalls gets all the calls that were made to AccessibleColors.
-// Check the length with:
-//
-//	len(mockedConfig.AccessibleColorsCalls())
-func (mock *ConfigMock) AccessibleColorsCalls() []struct {
-	Hostname string
-} {
-	var calls []struct {
-		Hostname string
-	}
-	mock.lockAccessibleColors.RLock()
-	calls = mock.calls.AccessibleColors
-	mock.lockAccessibleColors.RUnlock()
-	return calls
-}
-
 // Aliases calls AliasesFunc.
 func (mock *ConfigMock) Aliases() gh.AliasConfig {
 	if mock.AliasesFunc == nil {
diff --git a/internal/prompter/prompter_mock.go b/internal/prompter/prompter_mock.go
index b15f8bf96..b817a491f 100644
--- a/internal/prompter/prompter_mock.go
+++ b/internal/prompter/prompter_mock.go
@@ -20,28 +20,28 @@ var _ Prompter = &PrompterMock{}
 //			AuthTokenFunc: func() (string, error) {
 //				panic("mock out the AuthToken method")
 //			},
-//			ConfirmFunc: func(prompt string, defaultValue bool) (bool, error) {
+//			ConfirmFunc: func(s string, b bool) (bool, error) {
 //				panic("mock out the Confirm method")
 //			},
-//			ConfirmDeletionFunc: func(requiredValue string) error {
+//			ConfirmDeletionFunc: func(s string) error {
 //				panic("mock out the ConfirmDeletion method")
 //			},
-//			InputFunc: func(prompt string, defaultValue string) (string, error) {
+//			InputFunc: func(s1 string, s2 string) (string, error) {
 //				panic("mock out the Input method")
 //			},
 //			InputHostnameFunc: func() (string, error) {
 //				panic("mock out the InputHostname method")
 //			},
-//			MarkdownEditorFunc: func(prompt string, defaultValue string, blankAllowed bool) (string, error) {
+//			MarkdownEditorFunc: func(s1 string, s2 string, b bool) (string, error) {
 //				panic("mock out the MarkdownEditor method")
 //			},
 //			MultiSelectFunc: func(prompt string, defaults []string, options []string) ([]int, error) {
 //				panic("mock out the MultiSelect method")
 //			},
-//			PasswordFunc: func(prompt string) (string, error) {
+//			PasswordFunc: func(s string) (string, error) {
 //				panic("mock out the Password method")
 //			},
-//			SelectFunc: func(prompt string, defaultValue string, options []string) (int, error) {
+//			SelectFunc: func(s1 string, s2 string, strings []string) (int, error) {
 //				panic("mock out the Select method")
 //			},
 //		}
@@ -55,28 +55,28 @@ type PrompterMock struct {
 	AuthTokenFunc func() (string, error)
 
 	// ConfirmFunc mocks the Confirm method.
-	ConfirmFunc func(prompt string, defaultValue bool) (bool, error)
+	ConfirmFunc func(s string, b bool) (bool, error)
 
 	// ConfirmDeletionFunc mocks the ConfirmDeletion method.
-	ConfirmDeletionFunc func(requiredValue string) error
+	ConfirmDeletionFunc func(s string) error
 
 	// InputFunc mocks the Input method.
-	InputFunc func(prompt string, defaultValue string) (string, error)
+	InputFunc func(s1 string, s2 string) (string, error)
 
 	// InputHostnameFunc mocks the InputHostname method.
 	InputHostnameFunc func() (string, error)
 
 	// MarkdownEditorFunc mocks the MarkdownEditor method.
-	MarkdownEditorFunc func(prompt string, defaultValue string, blankAllowed bool) (string, error)
+	MarkdownEditorFunc func(s1 string, s2 string, b bool) (string, error)
 
 	// MultiSelectFunc mocks the MultiSelect method.
 	MultiSelectFunc func(prompt string, defaults []string, options []string) ([]int, error)
 
 	// PasswordFunc mocks the Password method.
-	PasswordFunc func(prompt string) (string, error)
+	PasswordFunc func(s string) (string, error)
 
 	// SelectFunc mocks the Select method.
-	SelectFunc func(prompt string, defaultValue string, options []string) (int, error)
+	SelectFunc func(s1 string, s2 string, strings []string) (int, error)
 
 	// calls tracks calls to the methods.
 	calls struct {
@@ -85,34 +85,34 @@ type PrompterMock struct {
 		}
 		// Confirm holds details about calls to the Confirm method.
 		Confirm []struct {
-			// Prompt is the prompt argument value.
-			Prompt string
-			// DefaultValue is the defaultValue argument value.
-			DefaultValue bool
+			// S is the s argument value.
+			S string
+			// B is the b argument value.
+			B bool
 		}
 		// ConfirmDeletion holds details about calls to the ConfirmDeletion method.
 		ConfirmDeletion []struct {
-			// RequiredValue is the requiredValue argument value.
-			RequiredValue string
+			// S is the s argument value.
+			S string
 		}
 		// Input holds details about calls to the Input method.
 		Input []struct {
-			// Prompt is the prompt argument value.
-			Prompt string
-			// DefaultValue is the defaultValue argument value.
-			DefaultValue string
+			// S1 is the s1 argument value.
+			S1 string
+			// S2 is the s2 argument value.
+			S2 string
 		}
 		// InputHostname holds details about calls to the InputHostname method.
 		InputHostname []struct {
 		}
 		// MarkdownEditor holds details about calls to the MarkdownEditor method.
 		MarkdownEditor []struct {
-			// Prompt is the prompt argument value.
-			Prompt string
-			// DefaultValue is the defaultValue argument value.
-			DefaultValue string
-			// BlankAllowed is the blankAllowed argument value.
-			BlankAllowed bool
+			// S1 is the s1 argument value.
+			S1 string
+			// S2 is the s2 argument value.
+			S2 string
+			// B is the b argument value.
+			B bool
 		}
 		// MultiSelect holds details about calls to the MultiSelect method.
 		MultiSelect []struct {
@@ -125,17 +125,17 @@ type PrompterMock struct {
 		}
 		// Password holds details about calls to the Password method.
 		Password []struct {
-			// Prompt is the prompt argument value.
-			Prompt string
+			// S is the s argument value.
+			S string
 		}
 		// Select holds details about calls to the Select method.
 		Select []struct {
-			// Prompt is the prompt argument value.
-			Prompt string
-			// DefaultValue is the defaultValue argument value.
-			DefaultValue string
-			// Options is the options argument value.
-			Options []string
+			// S1 is the s1 argument value.
+			S1 string
+			// S2 is the s2 argument value.
+			S2 string
+			// Strings is the strings argument value.
+			Strings []string
 		}
 	}
 	lockAuthToken       sync.RWMutex
@@ -177,21 +177,21 @@ func (mock *PrompterMock) AuthTokenCalls() []struct {
 }
 
 // Confirm calls ConfirmFunc.
-func (mock *PrompterMock) Confirm(prompt string, defaultValue bool) (bool, error) {
+func (mock *PrompterMock) Confirm(s string, b bool) (bool, error) {
 	if mock.ConfirmFunc == nil {
 		panic("PrompterMock.ConfirmFunc: method is nil but Prompter.Confirm was just called")
 	}
 	callInfo := struct {
-		Prompt       string
-		DefaultValue bool
+		S string
+		B bool
 	}{
-		Prompt:       prompt,
-		DefaultValue: defaultValue,
+		S: s,
+		B: b,
 	}
 	mock.lockConfirm.Lock()
 	mock.calls.Confirm = append(mock.calls.Confirm, callInfo)
 	mock.lockConfirm.Unlock()
-	return mock.ConfirmFunc(prompt, defaultValue)
+	return mock.ConfirmFunc(s, b)
 }
 
 // ConfirmCalls gets all the calls that were made to Confirm.
@@ -199,12 +199,12 @@ func (mock *PrompterMock) Confirm(prompt string, defaultValue bool) (bool, error
 //
 //	len(mockedPrompter.ConfirmCalls())
 func (mock *PrompterMock) ConfirmCalls() []struct {
-	Prompt       string
-	DefaultValue bool
+	S string
+	B bool
 } {
 	var calls []struct {
-		Prompt       string
-		DefaultValue bool
+		S string
+		B bool
 	}
 	mock.lockConfirm.RLock()
 	calls = mock.calls.Confirm
@@ -213,19 +213,19 @@ func (mock *PrompterMock) ConfirmCalls() []struct {
 }
 
 // ConfirmDeletion calls ConfirmDeletionFunc.
-func (mock *PrompterMock) ConfirmDeletion(requiredValue string) error {
+func (mock *PrompterMock) ConfirmDeletion(s string) error {
 	if mock.ConfirmDeletionFunc == nil {
 		panic("PrompterMock.ConfirmDeletionFunc: method is nil but Prompter.ConfirmDeletion was just called")
 	}
 	callInfo := struct {
-		RequiredValue string
+		S string
 	}{
-		RequiredValue: requiredValue,
+		S: s,
 	}
 	mock.lockConfirmDeletion.Lock()
 	mock.calls.ConfirmDeletion = append(mock.calls.ConfirmDeletion, callInfo)
 	mock.lockConfirmDeletion.Unlock()
-	return mock.ConfirmDeletionFunc(requiredValue)
+	return mock.ConfirmDeletionFunc(s)
 }
 
 // ConfirmDeletionCalls gets all the calls that were made to ConfirmDeletion.
@@ -233,10 +233,10 @@ func (mock *PrompterMock) ConfirmDeletion(requiredValue string) error {
 //
 //	len(mockedPrompter.ConfirmDeletionCalls())
 func (mock *PrompterMock) ConfirmDeletionCalls() []struct {
-	RequiredValue string
+	S string
 } {
 	var calls []struct {
-		RequiredValue string
+		S string
 	}
 	mock.lockConfirmDeletion.RLock()
 	calls = mock.calls.ConfirmDeletion
@@ -245,21 +245,21 @@ func (mock *PrompterMock) ConfirmDeletionCalls() []struct {
 }
 
 // Input calls InputFunc.
-func (mock *PrompterMock) Input(prompt string, defaultValue string) (string, error) {
+func (mock *PrompterMock) Input(s1 string, s2 string) (string, error) {
 	if mock.InputFunc == nil {
 		panic("PrompterMock.InputFunc: method is nil but Prompter.Input was just called")
 	}
 	callInfo := struct {
-		Prompt       string
-		DefaultValue string
+		S1 string
+		S2 string
 	}{
-		Prompt:       prompt,
-		DefaultValue: defaultValue,
+		S1: s1,
+		S2: s2,
 	}
 	mock.lockInput.Lock()
 	mock.calls.Input = append(mock.calls.Input, callInfo)
 	mock.lockInput.Unlock()
-	return mock.InputFunc(prompt, defaultValue)
+	return mock.InputFunc(s1, s2)
 }
 
 // InputCalls gets all the calls that were made to Input.
@@ -267,12 +267,12 @@ func (mock *PrompterMock) Input(prompt string, defaultValue string) (string, err
 //
 //	len(mockedPrompter.InputCalls())
 func (mock *PrompterMock) InputCalls() []struct {
-	Prompt       string
-	DefaultValue string
+	S1 string
+	S2 string
 } {
 	var calls []struct {
-		Prompt       string
-		DefaultValue string
+		S1 string
+		S2 string
 	}
 	mock.lockInput.RLock()
 	calls = mock.calls.Input
@@ -308,23 +308,23 @@ func (mock *PrompterMock) InputHostnameCalls() []struct {
 }
 
 // MarkdownEditor calls MarkdownEditorFunc.
-func (mock *PrompterMock) MarkdownEditor(prompt string, defaultValue string, blankAllowed bool) (string, error) {
+func (mock *PrompterMock) MarkdownEditor(s1 string, s2 string, b bool) (string, error) {
 	if mock.MarkdownEditorFunc == nil {
 		panic("PrompterMock.MarkdownEditorFunc: method is nil but Prompter.MarkdownEditor was just called")
 	}
 	callInfo := struct {
-		Prompt       string
-		DefaultValue string
-		BlankAllowed bool
+		S1 string
+		S2 string
+		B  bool
 	}{
-		Prompt:       prompt,
-		DefaultValue: defaultValue,
-		BlankAllowed: blankAllowed,
+		S1: s1,
+		S2: s2,
+		B:  b,
 	}
 	mock.lockMarkdownEditor.Lock()
 	mock.calls.MarkdownEditor = append(mock.calls.MarkdownEditor, callInfo)
 	mock.lockMarkdownEditor.Unlock()
-	return mock.MarkdownEditorFunc(prompt, defaultValue, blankAllowed)
+	return mock.MarkdownEditorFunc(s1, s2, b)
 }
 
 // MarkdownEditorCalls gets all the calls that were made to MarkdownEditor.
@@ -332,14 +332,14 @@ func (mock *PrompterMock) MarkdownEditor(prompt string, defaultValue string, bla
 //
 //	len(mockedPrompter.MarkdownEditorCalls())
 func (mock *PrompterMock) MarkdownEditorCalls() []struct {
-	Prompt       string
-	DefaultValue string
-	BlankAllowed bool
+	S1 string
+	S2 string
+	B  bool
 } {
 	var calls []struct {
-		Prompt       string
-		DefaultValue string
-		BlankAllowed bool
+		S1 string
+		S2 string
+		B  bool
 	}
 	mock.lockMarkdownEditor.RLock()
 	calls = mock.calls.MarkdownEditor
@@ -388,19 +388,19 @@ func (mock *PrompterMock) MultiSelectCalls() []struct {
 }
 
 // Password calls PasswordFunc.
-func (mock *PrompterMock) Password(prompt string) (string, error) {
+func (mock *PrompterMock) Password(s string) (string, error) {
 	if mock.PasswordFunc == nil {
 		panic("PrompterMock.PasswordFunc: method is nil but Prompter.Password was just called")
 	}
 	callInfo := struct {
-		Prompt string
+		S string
 	}{
-		Prompt: prompt,
+		S: s,
 	}
 	mock.lockPassword.Lock()
 	mock.calls.Password = append(mock.calls.Password, callInfo)
 	mock.lockPassword.Unlock()
-	return mock.PasswordFunc(prompt)
+	return mock.PasswordFunc(s)
 }
 
 // PasswordCalls gets all the calls that were made to Password.
@@ -408,10 +408,10 @@ func (mock *PrompterMock) Password(prompt string) (string, error) {
 //
 //	len(mockedPrompter.PasswordCalls())
 func (mock *PrompterMock) PasswordCalls() []struct {
-	Prompt string
+	S string
 } {
 	var calls []struct {
-		Prompt string
+		S string
 	}
 	mock.lockPassword.RLock()
 	calls = mock.calls.Password
@@ -420,23 +420,23 @@ func (mock *PrompterMock) PasswordCalls() []struct {
 }
 
 // Select calls SelectFunc.
-func (mock *PrompterMock) Select(prompt string, defaultValue string, options []string) (int, error) {
+func (mock *PrompterMock) Select(s1 string, s2 string, strings []string) (int, error) {
 	if mock.SelectFunc == nil {
 		panic("PrompterMock.SelectFunc: method is nil but Prompter.Select was just called")
 	}
 	callInfo := struct {
-		Prompt       string
-		DefaultValue string
-		Options      []string
+		S1      string
+		S2      string
+		Strings []string
 	}{
-		Prompt:       prompt,
-		DefaultValue: defaultValue,
-		Options:      options,
+		S1:      s1,
+		S2:      s2,
+		Strings: strings,
 	}
 	mock.lockSelect.Lock()
 	mock.calls.Select = append(mock.calls.Select, callInfo)
 	mock.lockSelect.Unlock()
-	return mock.SelectFunc(prompt, defaultValue, options)
+	return mock.SelectFunc(s1, s2, strings)
 }
 
 // SelectCalls gets all the calls that were made to Select.
@@ -444,14 +444,14 @@ func (mock *PrompterMock) Select(prompt string, defaultValue string, options []s
 //
 //	len(mockedPrompter.SelectCalls())
 func (mock *PrompterMock) SelectCalls() []struct {
-	Prompt       string
-	DefaultValue string
-	Options      []string
+	S1      string
+	S2      string
+	Strings []string
 } {
 	var calls []struct {
-		Prompt       string
-		DefaultValue string
-		Options      []string
+		S1      string
+		S2      string
+		Strings []string
 	}
 	mock.lockSelect.RLock()
 	calls = mock.calls.Select
diff --git a/pkg/cmd/issue/edit/edit.go b/pkg/cmd/issue/edit/edit.go
index accea8add..8386cbcfa 100644
--- a/pkg/cmd/issue/edit/edit.go
+++ b/pkg/cmd/issue/edit/edit.go
@@ -5,9 +5,12 @@ import (
 	"net/http"
 	"sort"
 	"sync"
+	"time"
 
 	"github.com/MakeNowJust/heredoc"
 	"github.com/cli/cli/v2/api"
+	fd "github.com/cli/cli/v2/internal/featuredetection"
+	"github.com/cli/cli/v2/internal/gh"
 	"github.com/cli/cli/v2/internal/ghrepo"
 	"github.com/cli/cli/v2/internal/text"
 	shared "github.com/cli/cli/v2/pkg/cmd/issue/shared"
@@ -22,6 +25,7 @@ type EditOptions struct {
 	IO         *iostreams.IOStreams
 	BaseRepo   func() (ghrepo.Interface, error)
 	Prompter   prShared.EditPrompter
+	Detector   fd.Detector
 
 	DetermineEditor    func() (string, error)
 	FieldsToEditSurvey func(prShared.EditPrompter, *prShared.Editable) error
@@ -201,7 +205,18 @@ func editRun(opts *EditOptions) error {
 		lookupFields = append(lookupFields, "labels")
 	}
 	if editable.Projects.Edited {
-		lookupFields = append(lookupFields, "projectCards")
+		// TODO projectsV1Deprecation
+		// Remove this section as we should no longer add projectCards
+		if opts.Detector == nil {
+			cachedClient := api.NewCachedHTTPClient(httpClient, time.Hour*24)
+			opts.Detector = fd.NewDetector(cachedClient, baseRepo.RepoHost())
+		}
+
+		projectsV1Support := opts.Detector.ProjectsV1()
+		if projectsV1Support == gh.ProjectsV1Supported {
+			lookupFields = append(lookupFields, "projectCards")
+		}
+
 		lookupFields = append(lookupFields, "projectItems")
 	}
 	if editable.Milestone.Edited {
diff --git a/pkg/cmd/root/help_topic.go b/pkg/cmd/root/help_topic.go
index 0c9534306..4b692777c 100644
--- a/pkg/cmd/root/help_topic.go
+++ b/pkg/cmd/root/help_topic.go
@@ -84,8 +84,6 @@ var HelpTopics = []helpTopic{
 			%[1]sGH_COLOR_LABELS%[1]s: set to any value to display labels using their RGB hex color codes in terminals that
 			support truecolor.
 
-			%[1]sGH_ACCESSIBLE_COLORS%[1]s (preview): set to a truthy value to use customizable, 4-bit accessible colors.
-
 			%[1]sGH_FORCE_TTY%[1]s: set to any value to force terminal-style output even when the output is
 			redirected. When the value is a number, it is interpreted as the number of columns
 			available in the viewport. When the value is a percentage, it will be applied against


Test patch:
diff --git a/pkg/cmd/config/list/list_test.go b/pkg/cmd/config/list/list_test.go
index 2a1dd72ee..2184d0f16 100644
--- a/pkg/cmd/config/list/list_test.go
+++ b/pkg/cmd/config/list/list_test.go
@@ -101,7 +101,6 @@ func Test_listRun(t *testing.T) {
 				http_unix_socket=
 				browser=brave
 				color_labels=disabled
-				accessible_colors=disabled
 			`),
 		},
 	}
diff --git a/pkg/cmd/issue/edit/edit_test.go b/pkg/cmd/issue/edit/edit_test.go
index a9d43c3ec..c9aa4c409 100644
--- a/pkg/cmd/issue/edit/edit_test.go
+++ b/pkg/cmd/issue/edit/edit_test.go
@@ -10,7 +10,9 @@ import (
 
 	"github.com/MakeNowJust/heredoc"
 	"github.com/cli/cli/v2/api"
+	fd "github.com/cli/cli/v2/internal/featuredetection"
 	"github.com/cli/cli/v2/internal/ghrepo"
+	"github.com/cli/cli/v2/internal/run"
 	prShared "github.com/cli/cli/v2/pkg/cmd/pr/shared"
 	"github.com/cli/cli/v2/pkg/cmdutil"
 	"github.com/cli/cli/v2/pkg/httpmock"
@@ -788,3 +790,88 @@ func mockProjectV2ItemUpdate(t *testing.T, reg *httpmock.Registry) {
 			func(inputs map[string]interface{}) {}),
 	)
 }
+
+// TODO projectsV1Deprecation
+// Remove this test.
+func TestProjectsV1Deprecation(t *testing.T) {
+	t.Run("when projects v1 is supported, is included in query", func(t *testing.T) {
+		ios, _, _, _ := iostreams.Test()
+
+		reg := &httpmock.Registry{}
+		reg.Register(
+			httpmock.GraphQL(`projectCards`),
+			// Simulate a GraphQL error to early exit the test.
+			httpmock.StatusStringResponse(500, ""),
+		)
+
+		_, cmdTeardown := run.Stub()
+		defer cmdTeardown(t)
+
+		// Ignore the error because we have no way to really stub it without
+		// fully stubbing a GQL error structure in the request body.
+		_ = editRun(&EditOptions{
+			IO: ios,
+			HttpClient: func() (*http.Client, error) {
+				return &http.Client{Transport: reg}, nil
+			},
+			BaseRepo: func() (ghrepo.Interface, error) {
+				return ghrepo.New("OWNER", "REPO"), nil
+			},
+			Detector: &fd.EnabledDetectorMock{},
+
+			IssueNumbers: []int{123},
+			Editable: prShared.Editable{
+				Projects: prShared.EditableProjects{
+					EditableSlice: prShared.EditableSlice{
+						Add:    []string{"Test Project"},
+						Edited: true,
+					},
+				},
+			},
+		})
+
+		// Verify that our request contained projectCards
+		reg.Verify(t)
+	})
+
+	t.Run("when projects v1 is not supported, is not included in query", func(t *testing.T) {
+		ios, _, _, _ := iostreams.Test()
+
+		reg := &httpmock.Registry{}
+		reg.Exclude(t, httpmock.GraphQL(`projectCards`))
+
+		reg.Register(
+			httpmock.GraphQL(`.*`),
+			// Simulate a GraphQL error to early exit the test.
+			httpmock.StatusStringResponse(500, ""),
+		)
+
+		_, cmdTeardown := run.Stub()
+		defer cmdTeardown(t)
+
+		// Ignore the error because we're not really interested in it.
+		_ = editRun(&EditOptions{
+			IO: ios,
+			HttpClient: func() (*http.Client, error) {
+				return &http.Client{Transport: reg}, nil
+			},
+			BaseRepo: func() (ghrepo.Interface, error) {
+				return ghrepo.New("OWNER", "REPO"), nil
+			},
+			Detector: &fd.DisabledDetectorMock{},
+
+			IssueNumbers: []int{123},
+			Editable: prShared.Editable{
+				Projects: prShared.EditableProjects{
+					EditableSlice: prShared.EditableSlice{
+						Add:    []string{"Test Project"},
+						Edited: true,
+					},
+				},
+			},
+		})
+
+		// Verify that our request contained projectCards
+		reg.Verify(t)
+	})
+}