Repository: cli/cli

Issue:
Table headers are low contrast on default terminal app theme
### Describe the bug

I am using gh 2.38.0 (installed via Homebrew) in macOS Sonoma's Terminal.app with a black-on-white theme.  Styled text produced by `gh` is often very low-contrast against the background, and some searching pointed me to gh's `GLAMOUR_STYLE` environment variable for changing the colors.  Unfortunately, no matter what I set `GLAMOUR_STYLE` to — [`light`, `dark`, `notty`, or `dracula`](https://github.com/charmbracelet/glamour/tree/master/styles/gallery) — the output colors remain unchanged and unreadable.

Output from `gh repo list` (no `GLAMOUR_STYLE`):

<img width="617" alt="default" src="https://github.com/cli/cli/assets/98207/c205e0d0-1f07-4b9f-8a7b-32263cec9921">

Output from `GLAMOUR_STYLE=light gh repo list`:

<img width="617" alt="light" src="https://github.com/cli/cli/assets/98207/8b27ec65-ae7d-4a3f-ae6e-b4d26093cbcb">

Output from `GLAMOUR_STYLE=dark gh repo list`:

<img width="617" alt="dark" src="https://github.com/cli/cli/assets/98207/21ce20b0-83e0-4395-ba9a-84ef5968df46">

Hints from issue comments:
@samcoe: Hi @jwodder sorry you are experiencing this, but I think there is a misunderstanding of the purpose of the `GLAMOUR_STYLE` environment variable. From `gh help environment`:

> GLAMOUR_STYLE: the style to use for rendering Markdown. See
<https://github.com/charmbracelet/glamour#styles>

The `gh repo list` command is not rendering markdown and thus the `GLAMOUR_STYLE` does not come into effect. Commands such as `gh repo view` do render markdown and you will be able to see the effect of changing that environment variable. 

Having said that I agree that the output you are seeing is quite low contrast. We just introduced the table headers so perhaps we need to pick a different color to use there. Are you using one of the default color schemes in the terminal app? Or is this a color scheme you found/created?@jwodder: @samcoe You're right, `GLAMOUR_STYLE` does indeed work for `gh repo view`.

> Having said that I agree that the output you are seeing is quite low contrast. We just introduced the table headers so perhaps we need to pick a different color to use there. Are you using one of the default color schemes in the terminal app? Or is this a color scheme you found/created?

It's the unmodified builtin "Basic" theme (though it's possible the theme settings were carried over from an earlier macOS version and the theme now looks different in a fresh Sonoma install).@andyfeller: @jwodder : I wanted to circle back to this issue as `v2.70.0` release has changes to natively fix this issue based upon the light / dark appearance of the background.  If you had a moment, I'd appreciate your thoughts on the new experience as we strive to improve our user experience! 🙇 @jwodder: @andyfeller If I run `gh repo list` in a dark-on-light Terminal.app session, the results look fine:

<img width="617" alt="Image" src="https://github.com/user-attachments/assets/3e77f95c-4ca0-4cb5-b698-272c5b58b928" />

However, if I run the command inside a GNU Screen (v5.0.0) or tmux (v3.5a) session, `gh` seems to default to assuming a light-on-dark theme, which results in barely-visible table headers:

<img width="617" alt="Image" src="https://github.com/user-attachments/assets/a8d0b8df-b4c7-48da-b909-24dae7a7ad97" />

Note that I've since changed my actual day-to-day terminal environment to light-on-dark, so either way, this issue doesn't effect me normally anymore.@yousufmirpro: > ### Describe the bug
> I am using gh 2.38.0 (installed via Homebrew) in macOS Sonoma's Terminal.app with a black-on-white theme. Styled text produced by `gh` is often very low-contrast against the background, and some searching pointed me to gh's `GLAMOUR_STYLE` environment variable for changing the colors. Unfortunately, no matter what I set `GLAMOUR_STYLE` to — [`light`, `dark`, `notty`, or `dracula`](https://github.com/charmbracelet/glamour/tree/master/styles/gallery) — the output colors remain unchanged and unreadable.
> 
> Output from `gh repo list` (no `GLAMOUR_STYLE`):
> 
> <img alt="default" width="617" src="https://private-user-images.githubusercontent.com/98207/280421165-c205e0d0-1f07-4b9f-8a7b-32263cec9921.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDUzNjYwOTMsIm5iZiI6MTc0NTM2NTc5MywicGF0aCI6Ii85ODIwNy8yODA0MjExNjUtYzIwNWUwZDAtMWYwNy00YjlmLThhN2ItMzIyNjNjZWM5OTIxLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA0MjIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNDIyVDIzNDk1M1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTM3N2E1MTU4NzE3NTZmODY1NWU4MzA5N2ExNjM0ZjRiY2U2MmMwZTExODdiMDhlYzI3ODIyOTMwMTFlMGMxMTYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.WfFfn8Hl1uc10HN39QVW6BBFonJZTd0OAg87tBTVscY">
> Output from `GLAMOUR_STYLE=light gh repo list`:
> 
> <img alt="light" width="617" src="https://private-user-images.githubusercontent.com/98207/280421279-8b27ec65-ae7d-4a3f-ae6e-b4d26093cbcb.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDUzNjYwOTMsIm5iZiI6MTc0NTM2NTc5MywicGF0aCI6Ii85ODIwNy8yODA0MjEyNzktOGIyN2VjNjUtYWU3ZC00YTNmLWFlNmUtYjRkMjYwOTNjYmNiLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA0MjIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNDIyVDIzNDk1M1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWI1NmE0ZjgyYWU1MjcxNGE3NzQ0YWQ0ZTI0ZGVhZDFlZDk0YTlkMWY4MjQ1NTFmNmZkMWIxOTg4NGUzZDczYzkmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.AhlQKQ_Z1zctOL02_dD0w4qSJyO8evLo4sO5cz5GHAQ">
> Output from `GLAMOUR_STYLE=dark gh repo list`:
> 
> <img alt="dark" width="617" src="https://private-user-images.githubusercontent.com/98207/280421339-21ce20b0-83e0-4395-ba9a-84ef5968df46.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDUzNjYwOTMsIm5iZiI6MTc0NTM2NTc5MywicGF0aCI6Ii85ODIwNy8yODA0MjEzMzktMjFjZTIwYjAtODNlMC00Mzk1LWJhOWEtODRlZjU5NjhkZjQ2LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA0MjIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNDIyVDIzNDk1M1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTU1MDdkYmJiYWI1YWRlZTRhMTVlY2EwMGU2MzMyNjM0NzZiMWQ1OTY0NTZhODk3ZmQ4ZTYyODVkYmIzMjZlNTImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.c1bEoXOkIU20KWkSPfThI5ATmmgtU0Q7ZdfOSIBTImA">

https://github.com/cli/cli/issues/8292#issue-1976977292@andyfeller: > [@andyfeller](https://github.com/andyfeller) If I run `gh repo list` in a dark-on-light Terminal.app session, the results look fine:
> 
> <img alt="Image" width="617" src="https://private-user-images.githubusercontent.com/98207/433981058-3e77f95c-4ca0-4cb5-b698-272c5b58b928.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDcwMTk4MjEsIm5iZiI6MTc0NzAxOTUyMSwicGF0aCI6Ii85ODIwNy80MzM5ODEwNTgtM2U3N2Y5NWMtNGNhMC00Y2I1LWI2OTgtMjcyYzViNThiOTI4LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MTIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTEyVDAzMTIwMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWJiZDMzMjNkOTc2ZDMzNDMxZjc2ZWExMDc0ZWE1M2E0MzczZjhjODM1MDc4MWJkNmZjYWU2ZDBmOTUxZDIxNDQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.N3DgW3bGcfPoBtVxZB2O1T3AgoyxnsA489miu-YuJyE">
> However, if I run the command inside a GNU Screen (v5.0.0) or tmux (v3.5a) session, `gh` seems to default to assuming a light-on-dark theme, which results in barely-visible table headers:
> 
> <img alt="Image" width="617" src="https://private-user-images.githubusercontent.com/98207/433981375-a8d0b8df-b4c7-48da-b909-24dae7a7ad97.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDcwMTk4MjEsIm5iZiI6MTc0NzAxOTUyMSwicGF0aCI6Ii85ODIwNy80MzM5ODEzNzUtYThkMGI4ZGYtYjRjNy00OGRhLWI5MDktMjRkYWU3YTdhZDk3LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MTIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTEyVDAzMTIwMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTZiZjMxOTEyZmJjNzc5NjY1YTcyYzlmZjMyZjE5MTY2YWM4ZmFhMDJhNWZkNjA1MDU0YmExY2JkNzZlMDk1NWUmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.lcSO2gg9Lutg0GbPplm0oSjmcG3OfFyWDExRs7O5SCA">
> Note that I've since changed my actual day-to-day terminal environment to light-on-dark, so either way, this issue doesn't effect me normally anymore.

@jwodder : Thank you for the follow up, here 🤔 

This second screenshot had the new `accessible_colors` setting / `GH_ACCESSIBLE_COLORS` env var set, correct?  That or an older version would be the only reasons I could see the headers looking wrong here.@jwodder: @andyfeller I did not set that envvar, nor have I done anything with that setting.  I think I was using v2.68 of `gh` when I took those screenshots.

Solution patch:
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 4e2032a87..a1ed27d99 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -62,9 +62,9 @@ To propose a design:
 - Include a link to the issue that the design is for.
 - Describe the design you are proposing to resolve the issue, leveraging the [CLI Design System][].
 - Mock up the design you are proposing using our [Google Docs Template][] or code blocks.
-  - Mock ups should cleary illustrate the command(s) being run and the expected output(s).
+  - Mock ups should clearly illustrate the command(s) being run and the expected output(s).
 
-### (core team only) Revewing a design
+### (core team only) Reviewing a design
 
 A member of the core team will [triage](../docs/triage.md) the design proposal. Once a member of the core team has reviewed the design, they may add the [`help wanted`][hw] label to the issue, so a PR can be opened to provide the implementation.
 
diff --git a/internal/tableprinter/table_printer.go b/internal/tableprinter/table_printer.go
index e1454170f..69b22be12 100644
--- a/internal/tableprinter/table_printer.go
+++ b/internal/tableprinter/table_printer.go
@@ -80,7 +80,7 @@ func NewWithWriter(w io.Writer, isTTY bool, maxWidth int, cs *iostreams.ColorSch
 		tp.AddHeader(
 			upperCasedHeaders,
 			WithPadding(paddingFunc),
-			WithColor(cs.LightGrayUnderline),
+			WithColor(cs.TableHeader),
 		)
 	}
 
diff --git a/pkg/cmd/run/list/list.go b/pkg/cmd/run/list/list.go
index 3113fdabd..7b18c391d 100644
--- a/pkg/cmd/run/list/list.go
+++ b/pkg/cmd/run/list/list.go
@@ -62,6 +62,8 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman
 
 			Note that providing the %[1]sworkflow_name%[1]s to the %[1]s-w%[1]s flag will not fetch disabled workflows.
 			Also pass the %[1]s-a%[1]s flag to fetch disabled workflow runs using the %[1]sworkflow_name%[1]s and the %[1]s-w%[1]s flag.
+
+			Runs created by organization and enterprise ruleset workflows will not display a workflow name due to GitHub API limitations.
 		`, "`"),
 		Aliases: []string{"ls"},
 		Args:    cobra.NoArgs,
diff --git a/pkg/cmd/run/shared/shared.go b/pkg/cmd/run/shared/shared.go
index 040888ab5..ce909fd77 100644
--- a/pkg/cmd/run/shared/shared.go
+++ b/pkg/cmd/run/shared/shared.go
@@ -446,6 +446,18 @@ func preloadWorkflowNames(client *api.Client, repo ghrepo.Interface, runs []Run)
 		if _, ok := workflowMap[run.WorkflowID]; !ok {
 			// Look up workflow by ID because it may have been deleted
 			workflow, err := workflowShared.GetWorkflow(client, repo, run.WorkflowID)
+			// If the error is an httpError and it is a 404, this is likely a
+			// organization or enterprise ruleset workflow. The user does not
+			// have permissions to view the details of the workflow, so we cannot
+			// look it up directly without receiving a 404, but it is nonetheless
+			// in the workflow run list. To handle this, we set the workflow name
+			// to an empty string.
+			// Deciding to put this here instead of in GetWorkflow to allow
+			// the caller to decide what a 404 means.
+			if httpErr, ok := err.(api.HTTPError); ok && httpErr.StatusCode == 404 {
+				workflowMap[run.WorkflowID] = ""
+				continue
+			}
 			if err != nil {
 				return err
 			}
diff --git a/pkg/iostreams/color.go b/pkg/iostreams/color.go
index c8d48168f..07fdcd79f 100644
--- a/pkg/iostreams/color.go
+++ b/pkg/iostreams/color.go
@@ -9,34 +9,45 @@ import (
 )
 
 const (
+	NoTheme        = "none"
+	DarkTheme      = "dark"
+	LightTheme     = "light"
 	highlightStyle = "black:yellow"
 )
 
+// Special cases like darkThemeTableHeader / lightThemeTableHeader are necessary when using color and modifiers
+// (bold, underline, dim) because ansi.ColorFunc requires a foreground color and resets formats.
 var (
-	magenta            = ansi.ColorFunc("magenta")
-	cyan               = ansi.ColorFunc("cyan")
-	red                = ansi.ColorFunc("red")
-	yellow             = ansi.ColorFunc("yellow")
-	blue               = ansi.ColorFunc("blue")
-	green              = ansi.ColorFunc("green")
-	gray               = ansi.ColorFunc("black+h")
-	lightGrayUnderline = ansi.ColorFunc("white+du")
-	bold               = ansi.ColorFunc("default+b")
-	cyanBold           = ansi.ColorFunc("cyan+b")
-	greenBold          = ansi.ColorFunc("green+b")
-	highlightStart     = ansi.ColorCode(highlightStyle)
-	highlight          = ansi.ColorFunc(highlightStyle)
+	magenta               = ansi.ColorFunc("magenta")
+	cyan                  = ansi.ColorFunc("cyan")
+	red                   = ansi.ColorFunc("red")
+	yellow                = ansi.ColorFunc("yellow")
+	blue                  = ansi.ColorFunc("blue")
+	green                 = ansi.ColorFunc("green")
+	gray                  = ansi.ColorFunc("black+h")
+	bold                  = ansi.ColorFunc("default+b")
+	cyanBold              = ansi.ColorFunc("cyan+b")
+	greenBold             = ansi.ColorFunc("green+b")
+	highlightStart        = ansi.ColorCode(highlightStyle)
+	highlight             = ansi.ColorFunc(highlightStyle)
+	darkThemeTableHeader  = ansi.ColorFunc("white+du")
+	lightThemeTableHeader = ansi.ColorFunc("black+hu")
+	noThemeTableHeader    = ansi.ColorFunc("default+u")
 
 	gray256 = func(t string) string {
 		return fmt.Sprintf("\x1b[%d;5;%dm%s\x1b[m", 38, 242, t)
 	}
 )
 
-func NewColorScheme(enabled, is256enabled bool, trueColor bool) *ColorScheme {
+// NewColorScheme initializes color logic based on provided terminal capabilities.
+// Logic dealing with terminal theme detected, such as whether color is enabled, 8-bit color supported, true color supported,
+// and terminal theme detected.
+func NewColorScheme(enabled, is256enabled, trueColor bool, theme string) *ColorScheme {
 	return &ColorScheme{
 		enabled:      enabled,
 		is256enabled: is256enabled,
 		hasTrueColor: trueColor,
+		theme:        theme,
 	}
 }
 
@@ -44,6 +55,7 @@ type ColorScheme struct {
 	enabled      bool
 	is256enabled bool
 	hasTrueColor bool
+	theme        string
 }
 
 func (c *ColorScheme) Enabled() bool {
@@ -115,13 +127,6 @@ func (c *ColorScheme) Grayf(t string, args ...interface{}) string {
 	return c.Gray(fmt.Sprintf(t, args...))
 }
 
-func (c *ColorScheme) LightGrayUnderline(t string) string {
-	if !c.enabled {
-		return t
-	}
-	return lightGrayUnderline(t)
-}
-
 func (c *ColorScheme) Magenta(t string) string {
 	if !c.enabled {
 		return t
@@ -254,3 +259,19 @@ func (c *ColorScheme) HexToRGB(hex string, x string) string {
 	b, _ := strconv.ParseInt(hex[4:6], 16, 64)
 	return fmt.Sprintf("\033[38;2;%d;%d;%dm%s\033[0m", r, g, b, x)
 }
+
+func (c *ColorScheme) TableHeader(t string) string {
+	// Table headers are only stylized if color is enabled including underline modifier.
+	if !c.enabled {
+		return t
+	}
+
+	switch c.theme {
+	case DarkTheme:
+		return darkThemeTableHeader(t)
+	case LightTheme:
+		return lightThemeTableHeader(t)
+	default:
+		return noThemeTableHeader(t)
+	}
+}
diff --git a/pkg/iostreams/iostreams.go b/pkg/iostreams/iostreams.go
index 2bc712a3c..6c12d7911 100644
--- a/pkg/iostreams/iostreams.go
+++ b/pkg/iostreams/iostreams.go
@@ -366,7 +366,7 @@ func (s *IOStreams) TerminalWidth() int {
 }
 
 func (s *IOStreams) ColorScheme() *ColorScheme {
-	return NewColorScheme(s.ColorEnabled(), s.ColorSupport256(), s.HasTrueColor())
+	return NewColorScheme(s.ColorEnabled(), s.ColorSupport256(), s.HasTrueColor(), s.TerminalTheme())
 }
 
 func (s *IOStreams) ReadUserFile(fn string) ([]byte, error) {



Test patch:
diff --git a/pkg/cmd/alias/delete/delete_test.go b/pkg/cmd/alias/delete/delete_test.go
index 845119a80..9bc89830a 100644
--- a/pkg/cmd/alias/delete/delete_test.go
+++ b/pkg/cmd/alias/delete/delete_test.go
@@ -162,6 +162,9 @@ func TestDeleteRun(t *testing.T) {
 			tt.opts.IO = ios
 
 			cfg := config.NewFromString(tt.config)
+			cfg.WriteFunc = func() error {
+				return nil
+			}
 			tt.opts.Config = func() (gh.Config, error) {
 				return cfg, nil
 			}
diff --git a/pkg/cmd/gist/list/list_test.go b/pkg/cmd/gist/list/list_test.go
index 0acfae109..4f6c8a9f7 100644
--- a/pkg/cmd/gist/list/list_test.go
+++ b/pkg/cmd/gist/list/list_test.go
@@ -486,7 +486,7 @@ func Test_listRun(t *testing.T) {
 				)
 			},
 			wantOut: heredoc.Docf(`
-				%[1]s[0;2;4;37mID  %[1]s[0m  %[1]s[0;2;4;37mDESCRIPTION                  %[1]s[0m  %[1]s[0;2;4;37mFILES  %[1]s[0m  %[1]s[0;2;4;37mVISIBILITY%[1]s[0m  %[1]s[0;2;4;37mUPDATED          %[1]s[0m
+				%[1]s[0;4;39mID  %[1]s[0m  %[1]s[0;4;39mDESCRIPTION                  %[1]s[0m  %[1]s[0;4;39mFILES  %[1]s[0m  %[1]s[0;4;39mVISIBILITY%[1]s[0m  %[1]s[0;4;39mUPDATED          %[1]s[0m
 				1234  %[1]s[0;30;43mocto%[1]s[0m%[1]s[0;1;39m match in the description%[1]s[0m  1 file   %[1]s[0;32mpublic    %[1]s[0m  %[1]s[38;5;242mabout 6 hours ago%[1]s[m
 				2345  %[1]s[0;1;39mmatch in the file name       %[1]s[0m  %[1]s[0;30;43m2 files%[1]s[0m  %[1]s[0;31msecret    %[1]s[0m  %[1]s[38;5;242mabout 6 hours ago%[1]s[m
 			`, "\x1b"),
@@ -694,7 +694,7 @@ func Test_highlightMatch(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			cs := iostreams.NewColorScheme(tt.color, false, false)
+			cs := iostreams.NewColorScheme(tt.color, false, false, iostreams.NoTheme)
 
 			matched := false
 			got, err := highlightMatch(tt.input, regex, &matched, cs.Blue, cs.Highlight)
diff --git a/pkg/cmd/run/list/list_test.go b/pkg/cmd/run/list/list_test.go
index 7717c2ff9..0f11e492a 100644
--- a/pkg/cmd/run/list/list_test.go
+++ b/pkg/cmd/run/list/list_test.go
@@ -366,6 +366,44 @@ func TestListRun(t *testing.T) {
 				completed	stale	cool commit	CI	trunk	push	10	4m34s	2021-02-23T04:51:00Z
 			`),
 		},
+		{
+			name: "org ruleset workflow in runs list shows with empty workflow name",
+			opts: &ListOptions{
+				Limit: defaultLimit,
+				now:   shared.TestRunStartTime.Add(time.Minute*4 + time.Second*34),
+			},
+			isTTY: true,
+			stubs: func(reg *httpmock.Registry) {
+				reg.Register(
+					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
+					httpmock.JSONResponse(shared.RunsPayload{
+						WorkflowRuns: shared.TestRunsWithOrgRequiredWorkflows,
+					}))
+				reg.Register(
+					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
+					httpmock.JSONResponse(workflowShared.WorkflowsPayload{
+						Workflows: []workflowShared.Workflow{
+							shared.TestWorkflow,
+						},
+					}))
+				reg.Register(
+					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/456"),
+					httpmock.StatusStringResponse(404, "not found"),
+				)
+			},
+			wantOut: heredoc.Doc(`
+				STATUS  TITLE        WORKFLOW  BRANCH  EVENT  ID  ELAPSED  AGE
+				X       cool commit            trunk   push   1   4m34s    about 4 minutes ago
+				*       cool commit            trunk   push   2   4m34s    about 4 minutes ago
+				✓       cool commit            trunk   push   3   4m34s    about 4 minutes ago
+				X       cool commit            trunk   push   4   4m34s    about 4 minutes ago
+				X       cool commit  CI        trunk   push   5   4m34s    about 4 minutes ago
+				-       cool commit  CI        trunk   push   6   4m34s    about 4 minutes ago
+				-       cool commit  CI        trunk   push   7   4m34s    about 4 minutes ago
+				*       cool commit  CI        trunk   push   8   4m34s    about 4 minutes ago
+				*       cool commit  CI        trunk   push   9   4m34s    about 4 minutes ago
+			`),
+		},
 		{
 			name: "pagination",
 			opts: &ListOptions{
diff --git a/pkg/cmd/run/shared/test.go b/pkg/cmd/run/shared/test.go
index 7caa37039..0619541a4 100644
--- a/pkg/cmd/run/shared/test.go
+++ b/pkg/cmd/run/shared/test.go
@@ -18,6 +18,10 @@ func TestRunWithCommit(id int64, s Status, c Conclusion, commit string) Run {
 	return TestRunWithWorkflowAndCommit(123, id, s, c, commit)
 }
 
+func TestRunWithOrgRequiredWorkflow(id int64, s Status, c Conclusion, commit string) Run {
+	return TestRunWithWorkflowAndCommit(456, id, s, c, commit)
+}
+
 func TestRunWithWorkflowAndCommit(workflowId, runId int64, s Status, c Conclusion, commit string) Run {
 	return Run{
 		WorkflowID: workflowId,
@@ -57,6 +61,18 @@ var TestRuns []Run = []Run{
 	TestRun(10, Completed, Stale),
 }
 
+var TestRunsWithOrgRequiredWorkflows []Run = []Run{
+	TestRunWithOrgRequiredWorkflow(1, Completed, TimedOut, "cool commit"),
+	TestRunWithOrgRequiredWorkflow(2, InProgress, "", "cool commit"),
+	TestRunWithOrgRequiredWorkflow(3, Completed, Success, "cool commit"),
+	TestRunWithOrgRequiredWorkflow(4, Completed, Cancelled, "cool commit"),
+	TestRun(5, Completed, Failure),
+	TestRun(6, Completed, Neutral),
+	TestRun(7, Completed, Skipped),
+	TestRun(8, Requested, ""),
+	TestRun(9, Queued, ""),
+}
+
 var WorkflowRuns []Run = []Run{
 	TestRun(2, InProgress, ""),
 	SuccessfulRun,
diff --git a/pkg/iostreams/color_test.go b/pkg/iostreams/color_test.go
index 59fea53ed..b35c2eb73 100644
--- a/pkg/iostreams/color_test.go
+++ b/pkg/iostreams/color_test.go
@@ -1,6 +1,7 @@
 package iostreams
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -19,28 +20,28 @@ func TestColorFromRGB(t *testing.T) {
 			hex:   "fc0303",
 			text:  "red",
 			wants: "\033[38;2;252;3;3mred\033[0m",
-			cs:    NewColorScheme(true, true, true),
+			cs:    NewColorScheme(true, true, true, NoTheme),
 		},
 		{
 			name:  "no truecolor",
 			hex:   "fc0303",
 			text:  "red",
 			wants: "red",
-			cs:    NewColorScheme(true, true, false),
+			cs:    NewColorScheme(true, true, false, NoTheme),
 		},
 		{
 			name:  "no color",
 			hex:   "fc0303",
 			text:  "red",
 			wants: "red",
-			cs:    NewColorScheme(false, false, false),
+			cs:    NewColorScheme(false, false, false, NoTheme),
 		},
 		{
 			name:  "invalid hex",
 			hex:   "fc0",
 			text:  "red",
 			wants: "red",
-			cs:    NewColorScheme(false, false, false),
+			cs:    NewColorScheme(false, false, false, NoTheme),
 		},
 	}
 
@@ -63,28 +64,28 @@ func TestHexToRGB(t *testing.T) {
 			hex:   "fc0303",
 			text:  "red",
 			wants: "\033[38;2;252;3;3mred\033[0m",
-			cs:    NewColorScheme(true, true, true),
+			cs:    NewColorScheme(true, true, true, NoTheme),
 		},
 		{
 			name:  "no truecolor",
 			hex:   "fc0303",
 			text:  "red",
 			wants: "red",
-			cs:    NewColorScheme(true, true, false),
+			cs:    NewColorScheme(true, true, false, NoTheme),
 		},
 		{
 			name:  "no color",
 			hex:   "fc0303",
 			text:  "red",
 			wants: "red",
-			cs:    NewColorScheme(false, false, false),
+			cs:    NewColorScheme(false, false, false, NoTheme),
 		},
 		{
 			name:  "invalid hex",
 			hex:   "fc0",
 			text:  "red",
 			wants: "red",
-			cs:    NewColorScheme(false, false, false),
+			cs:    NewColorScheme(false, false, false, NoTheme),
 		},
 	}
 
@@ -93,3 +94,84 @@ func TestHexToRGB(t *testing.T) {
 		assert.Equal(t, tt.wants, output)
 	}
 }
+
+func TestTableHeader(t *testing.T) {
+	reset := "\x1b[0m"
+	defaultUnderline := "\x1b[0;4;39m"
+	brightBlackUnderline := "\x1b[0;4;90m"
+	dimBlackUnderline := "\x1b[0;2;4;37m"
+
+	tests := []struct {
+		name     string
+		cs       *ColorScheme
+		input    string
+		expected string
+	}{
+		{
+			name:     "when color is disabled, text is not stylized",
+			cs:       NewColorScheme(false, false, false, NoTheme),
+			input:    "this should not be stylized",
+			expected: "this should not be stylized",
+		},
+		{
+			name:     "when 4-bit color is enabled but no theme, 4-bit default color and underline are used",
+			cs:       NewColorScheme(true, false, false, NoTheme),
+			input:    "this should have no explicit color but underlined",
+			expected: fmt.Sprintf("%sthis should have no explicit color but underlined%s", defaultUnderline, reset),
+		},
+		{
+			name:     "when 4-bit color is enabled and theme is light, 4-bit dark color and underline are used",
+			cs:       NewColorScheme(true, false, false, LightTheme),
+			input:    "this should have dark foreground color and underlined",
+			expected: fmt.Sprintf("%sthis should have dark foreground color and underlined%s", brightBlackUnderline, reset),
+		},
+		{
+			name:     "when 4-bit color is enabled and theme is dark, 4-bit light color and underline are used",
+			cs:       NewColorScheme(true, false, false, DarkTheme),
+			input:    "this should have light foreground color and underlined",
+			expected: fmt.Sprintf("%sthis should have light foreground color and underlined%s", dimBlackUnderline, reset),
+		},
+		{
+			name:     "when 8-bit color is enabled but no theme, 4-bit default color and underline are used",
+			cs:       NewColorScheme(true, true, false, NoTheme),
+			input:    "this should have no explicit color but underlined",
+			expected: fmt.Sprintf("%sthis should have no explicit color but underlined%s", defaultUnderline, reset),
+		},
+		{
+			name:     "when 8-bit color is enabled and theme is light, 4-bit dark color and underline are used",
+			cs:       NewColorScheme(true, true, false, LightTheme),
+			input:    "this should have dark foreground color and underlined",
+			expected: fmt.Sprintf("%sthis should have dark foreground color and underlined%s", brightBlackUnderline, reset),
+		},
+		{
+			name:     "when 8-bit color is true and theme is dark, 4-bit light color and underline are used",
+			cs:       NewColorScheme(true, true, false, DarkTheme),
+			input:    "this should have light foreground color and underlined",
+			expected: fmt.Sprintf("%sthis should have light foreground color and underlined%s", dimBlackUnderline, reset),
+		},
+		{
+			name:     "when 24-bit color is enabled but no theme, 4-bit default color and underline are used",
+			cs:       NewColorScheme(true, true, true, NoTheme),
+			input:    "this should have no explicit color but underlined",
+			expected: fmt.Sprintf("%sthis should have no explicit color but underlined%s", defaultUnderline, reset),
+		},
+		{
+			name:     "when 24-bit color is enabled and theme is light, 4-bit dark color and underline are used",
+			cs:       NewColorScheme(true, true, true, LightTheme),
+			input:    "this should have dark foreground color and underlined",
+			expected: fmt.Sprintf("%sthis should have dark foreground color and underlined%s", brightBlackUnderline, reset),
+		},
+		{
+			name:     "when 24-bit color is true and theme is dark, 4-bit light color and underline are used",
+			cs:       NewColorScheme(true, true, true, DarkTheme),
+			input:    "this should have light foreground color and underlined",
+			expected: fmt.Sprintf("%sthis should have light foreground color and underlined%s", dimBlackUnderline, reset),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			assert.Equal(t, tt.expected, tt.cs.TableHeader(tt.input))
+		})
+	}
+}