You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
gh pr list doesn't write output anywhere if stdout or stderr is piped #9152
When you pipe the stdout or stderr of gh somewhere, it no longer writes useful output to the stderr.
Steps to reproduce the behavior
❯ gh pr listX No default remote repository has been set for this directory.please run `gh repo set-default` to select a default remote repository.
❯ gh pr list | cat
❯
Using latest brew gh version:
❯ gh --versiongh version 2.50.0 (2024-05-29)https://github.com/cli/cli/releases/tag/v2.50.0
Expected vs actual behavior
I expect to see the error on the stderr even when I save the output on the stdout.
Hey @gibfahn, this took me a moment to track down so I can understand why you would be confused. This isn't a case of the output disappearing as you suspected but actually it's a behavioural difference in the CLI such that we don't treat the second case as an error.
In the first case, where we know that there is interactivity, the CLI bails out saying "we really want to you to choose a default repo for us to target" (see: gh repo set-default --help for what that means). In the second case, we're not sure whether there ever will be interactivity (e.g. in CI) so we select a remote to act as the default. To be honest I don't have the full picture on this since the code was written some 4 years ago but that's what's going on here. This would have been more visible if the remote being chosen had PRs, where yours presumably has none (you can check the specific API requests using GH_DEBUG=api)
I'd have to check whether there are other issues around the decision above and I'm happy to go searching for them if you still have a problem but I'm going to close this for now since the specific issue is addressed. Cheers.
Ahh okay, I guess trying to simplify the output didn't fully repro, how about this one?
❯ GH_REPO=gibfahn/brew GH_HOST=github.com gh pr list --state=open --limit=1no open pull requests in gibfahn/brew
❯ GH_REPO=gibfahn/brew GH_HOST=github.com gh pr list --state=open --limit=1 | cat
❯
I expect to still see the log message when something goes wrong.
For context I'm seeing this in a script where I'm saving the output to a shell variable to work around #575 . When the command doesn't return any results, it's useful to be able to see what went wrong.
GH_DEBUG doesn't cause this to be shown either for what it's worth:
❯ GH_DEBUG=1 GH_REPO=gibfahn/brew GH_HOST=github.com gh pr list '--state=open''--limit=1'| cat* Request at 2024-05-31 15:21:01.749336 +0100 BST m=+0.144867168* Request to https://api.github.com/graphql* Request took 427.841208ms
❯
Consider a workflow "find me all the open PRs and assign me as a reviewer". From a list command point of view, we don't know whether zero results is an error. Only you know if it is an error in your domain as the caller of the script.
When hooked up to a terminal, we try to provide a helpful message. I could see us printing something to stderr when there is no TTY but I suspect the reason Mislav didn't suggest that is because it would be a breaking change for any scripts that might be redirecting or consuming stderr as well. We try to be very careful about what we output to the iostreams when there is no TTY attached. Hyrum's Law and all.
GH_DEBUG doesn't cause this to be shown either for what it's worth:
This was just to highlight the repo that was being targeted, to ensure that it was the one you expected given my explanation of repo set-default.
The reasoning behind each of these decisions does make sense (thank you for sharing it!), but overall it leads to quite a confusing debugging experience, where my script fails because there was no output, so I try to change the command, and then it starts printing errors, and I change it again, and now it prints different errors, and none of them are the actual thing I need to debug (which ends up being that the upstream for the current branch is configured wrong, so there was no output). This gets more complex when you start using the --json --jq outputs, as there are more things that could have gone wrong (e.g. maybe the JSON format changed).
At the risk of adding more complexity, I would happily take an option here that made gh pr list always print the error message when nothing was found. In general in CI or automation is where I most want to see errors or more info on stderr, as it's where it's hardest to easily rerun the command to debug.
I came across this issue while searching for a similar problem with no errors involved. I'm just trying to get the output from gh pr list to merge many PRs at once. The command itself runs fine:
> gh pr list -A 'dependabot[bot]' --json headRefName --jq '.[].headRefName'
dependabot/cargo/clap-4.5.6
dependabot/cargo/spectro-80b1dec
dependabot/cargo/serde_json-1.0.117
dependabot/cargo/nalgebra-0.32.5
dependabot/cargo/psqs-d2ea8fe
But trying to pipe it anywhere (| cat, > out 2> err, var=$(...)) produces no output. I'm also on version 2.50 installed from the Arch repos.
In my case, I think the issue is something with the -A flag because this actually works as expected:
I just tried gh pr list -A 'dependabot[bot]' | cat in the Emacs shell running bash instead of my normal st running zsh to rule out a specific shell/terminal issue and had the same result. With | cat produces no output, without | cat runs fine.
Woah, but a different repo does work! This is very strange.
whereas the no-cat version has multiple queries, starting with
GraphQL query:
fragment repo on Repository {
id
name
owner { login }
viewerPermission
defaultBranchRef {
name
}
isPrivate
}
query RepositoryNetwork {
viewer { login }
repo_000: repository(owner: "ntBre", name: "rust-pbqff") {
...repo
parent {
...repo
}
}
}
GraphQL variables: null
and then the PullRequest query, which returns data in that case. All of the requests are returning HTTP 200.
I can upload the output from the command if that would help, but redirecting it to a file means I can only get the "cat" version. I guess I could install a terminal with better scrolling and paste the other output if needed.
Is there anything I should redact from the output before uploading? The token looks like it's already blocked out, and the only other thing I see that is possibly sensitive is the X-Oauth-Client-Id, but I think even that is probably okay (?)
Awesome, thanks! These are all public repos and the PRs are all from Dependabot (which is why I'm hoping to automate this a bit). Here's the full | cat version:
I'm thinking that there's a redirect going on here that is causing some confusion. Would it be fair to say that your git remote is still set to the old version? What's in git remote -v?
It seems like the search infra doesn't account for redirects while indexing. Would you mind creating an issue on this repo quickly summarising what we've found here (I would but it's pretty late here already!). I'm not sure what our options are here but I'd like to think about it next week.
When hooked up to a terminal, we try to provide a helpful message. I could see us printing something to stderr when there is no TTY but I suspect the reason Mislav didn't suggest that is because it would be a breaking change for any scripts that might be redirecting or consuming stderr as well. We try to be very careful about what we output to the iostreams when there is no TTY attached. Hyrum's Law and all.
Hoping to ground this somewhere in design artifacts, I think the only 2 I could really find are:
So it is a small comfort at least this much is documented though not necessarily the reasoning why. That said, I don't think screen output is a hard contract as structured data would be the less brittle route.
@andyfeller This is a good discussion, thanks for the resources! It seems those guidelines are related to machine-readability of output when there is no TTY, which gh does well. But writing to stderr doesn't effect machine-readability of stdout.
I think in general it is good practice to print to stderr, even when there is no TTY. I usually reference clig.dev for design conventions, and their Output section mentions:
It’s rare that printing nothing at all is the best default behavior, but it’s usually best to err on the side of less.
For instances where you do want no output (for example, when used in shell scripts), to avoid clumsy redirection of stderr to /dev/null, you can provide a -q option to suppress all non-essential output.
Additionally, when discussing where color should be disabled, one of the points is:
stdout or stderr is not an interactive terminal (a TTY). It’s best to individually check—if you’re piping stdout to another program, it’s still useful to get colors on stderr.
That implies it's good practice to print to stderr, even when there's no TTY.
A good example would be checking the logs when using gh in GitHub Actions and other continuous integration.
However, I also agree that breaking changes are bad and I'm not sure if this is worth one. It could be worth considering the CI environment variable, which is automatically set in GitHub Actions and other tools. Or GH_CI to be cautious of breaking changes. For example, when CI is set to any value and there is no TTY, I'd suggest:
Printing to stderr as if there were a TTY, including color. Color could still be disabled with NO_COLORCLICOLOR, etc.
Keeping stdout machine readable like it currently is when there is no TTY
I came here starting with similar issues of weirdly different behavior of gh commands. This isn't limited to gh pr list; I also encountered it with gh repo view. It also changes not only the output, but also the exit code.
$ gh repo view
X No default remote repository has been setfor this directory.
please run `gh repo set-default` to selecta default remote repository.
$ echo$?
1
$ gh repo view > /dev/null
$ echo$?
0
I understand it's nice to provide the most useful interface for a given use case, but I really dislike the pattern where a tool does different things depending on how you look at it (colored output is perhaps an exception). It seems totally backwards to me that in scripting it would try to guess what repo to use: I want my scripts to be debuggable and explicit and this is the exact opposite. If you want to have something by-default interactive, then I think the better way to do it is to have the tool fail in the non-interactive case and require that you explicitly pass a flag (e.g. -y for apt and pip).
Probably the ship has sailed, but I find myself hitting these kinds of issues every time I try to use gh for scripting. It massively increases the complexity of the tool and creates all kinds of weird footguns.
Activity
williammartin commentedon May 31, 2024
Hey @gibfahn, this took me a moment to track down so I can understand why you would be confused. This isn't a case of the output disappearing as you suspected but actually it's a behavioural difference in the CLI such that we don't treat the second case as an error.
In the first case, where we know that there is interactivity, the CLI bails out saying "we really want to you to choose a default repo for us to target" (see:
gh repo set-default --helpfor what that means). In the second case, we're not sure whether there ever will be interactivity (e.g. in CI) so we select a remote to act as the default. To be honest I don't have the full picture on this since the code was written some 4 years ago but that's what's going on here. This would have been more visible if the remote being chosen had PRs, where yours presumably has none (you can check the specific API requests usingGH_DEBUG=api)I'd have to check whether there are other issues around the decision above and I'm happy to go searching for them if you still have a problem but I'm going to close this for now since the specific issue is addressed. Cheers.
gibfahn commentedon May 31, 2024
Ahh okay, I guess trying to simplify the output didn't fully repro, how about this one?
I expect to still see the log message when something goes wrong.
For context I'm seeing this in a script where I'm saving the output to a shell variable to work around #575 . When the command doesn't return any results, it's useful to be able to see what went wrong.
GH_DEBUGdoesn't cause this to be shown either for what it's worth:williammartin commentedon May 31, 2024
Thank you for the clarification. I'll have to think about this and a comment from a previous maintainer a bit more.
Consider a workflow "find me all the open PRs and assign me as a reviewer". From a
listcommand point of view, we don't know whether zero results is an error. Only you know if it is an error in your domain as the caller of the script.When hooked up to a terminal, we try to provide a helpful message. I could see us printing something to
stderrwhen there is no TTY but I suspect the reason Mislav didn't suggest that is because it would be a breaking change for any scripts that might be redirecting or consumingstderras well. We try to be very careful about what we output to the iostreams when there is no TTY attached. Hyrum's Law and all.This was just to highlight the repo that was being targeted, to ensure that it was the one you expected given my explanation of
repo set-default.gibfahn commentedon May 31, 2024
The reasoning behind each of these decisions does make sense (thank you for sharing it!), but overall it leads to quite a confusing debugging experience, where my script fails because there was no output, so I try to change the command, and then it starts printing errors, and I change it again, and now it prints different errors, and none of them are the actual thing I need to debug (which ends up being that the upstream for the current branch is configured wrong, so there was no output). This gets more complex when you start using the
--json --jqoutputs, as there are more things that could have gone wrong (e.g. maybe the JSON format changed).At the risk of adding more complexity, I would happily take an option here that made
gh pr listalways print the error message when nothing was found. In general in CI or automation is where I most want to see errors or more info on stderr, as it's where it's hardest to easily rerun the command to debug.ntBre commentedon Jun 7, 2024
I came across this issue while searching for a similar problem with no errors involved. I'm just trying to get the output from
gh pr listto merge many PRs at once. The command itself runs fine:But trying to pipe it anywhere (
| cat,> out 2> err,var=$(...)) produces no output. I'm also on version 2.50 installed from the Arch repos.In my case, I think the issue is something with the
-Aflag because this actually works as expected:williammartin commentedon Jun 7, 2024
That's incredibly strange! Are you saying the following command produces no output for you, and without
| catit does?ntBre commentedon Jun 7, 2024
Yes, that's exactly right!
ntBre commentedon Jun 7, 2024
I just tried
gh pr list -A 'dependabot[bot]' | catin the Emacs shell running bash instead of my normalstrunning zsh to rule out a specific shell/terminal issue and had the same result. With| catproduces no output, without| catruns fine.Woah, but a different repo does work! This is very strange.
williammartin commentedon Jun 7, 2024
That's uhhh interesting. Can you see if there's anything interesting with
GH_DEBUG=apiturned on? I can't even fathom what's going on here.ntBre commentedon Jun 7, 2024
I'm not quite sure what I'm looking for, but the GraphQL query in the
cat versionisand the final data is empty:
whereas the no-cat version has multiple queries, starting with
and then the PullRequest query, which returns data in that case. All of the requests are returning HTTP 200.
I can upload the output from the command if that would help, but redirecting it to a file means I can only get the "cat" version. I guess I could install a terminal with better scrolling and paste the other output if needed.
williammartin commentedon Jun 7, 2024
Are the query and variables exactly the same? From your first log:
The full output would be useful. You may be able to force it to output by setting
GH_FORCE_TTY=truein your environment.ntBre commentedon Jun 7, 2024
Is there anything I should redact from the output before uploading? The token looks like it's already blocked out, and the only other thing I see that is possibly sensitive is the
X-Oauth-Client-Id, but I think even that is probably okay (?)williammartin commentedon Jun 7, 2024
Nothing sensitive unless there's anything that you want to redact from your own data (repo names, issues, authors, and that kind of thing).
ntBre commentedon Jun 7, 2024
Awesome, thanks! These are all public repos and the PRs are all from Dependabot (which is why I'm hoping to automate this a bit). Here's the full
| catversion:and no cat:
williammartin commentedon Jun 7, 2024
Well, these are indeed searching different repos:
vs
I'm thinking that there's a redirect going on here that is causing some confusion. Would it be fair to say that your
git remoteis still set to the old version? What's ingit remote -v?ntBre commentedon Jun 7, 2024
Ahhh, that makes sense. I did rename this repo a while back.
git remote -vreportsntBre/rust-pbqff(the old version) like you expected!williammartin commentedon Jun 7, 2024
It seems like the search infra doesn't account for redirects while indexing. Would you mind creating an issue on this repo quickly summarising what we've found here (I would but it's pretty late here already!). I'm not sure what our options are here but I'd like to think about it next week.
Sorry for all the noise @gibfahn ! 😬
ntBre commentedon Jun 7, 2024
Will do, thanks for the help! And yes, sorry for the noise. I thought this would be more relevant at first!
andyfeller commentedon Jun 13, 2024
Hoping to ground this somewhere in design artifacts, I think the only 2 I could really find are:
Primer Style > CLI > Components > Header
Primer Style > CLI > Foundations > Differences to note in machine output
YouTube "Designing for the command line - Amanda Pinsker"
So it is a small comfort at least this much is documented though not necessarily the reasoning why. That said, I don't think screen output is a hard contract as structured data would be the less brittle route.
benelan commentedon Jun 27, 2024
Bringing the discussion back to the original report, @gibfahn you could do this:
EDITS:
NO_COLORto avoid ANSI escape sequences (seegh help environmentfor more info)/dev/nullso it only outputs the log messages.benelan commentedon Jun 27, 2024
@andyfeller This is a good discussion, thanks for the resources! It seems those guidelines are related to machine-readability of output when there is no TTY, which
ghdoes well. But writing tostderrdoesn't effect machine-readability ofstdout.I think in general it is good practice to print to
stderr, even when there is no TTY. I usually reference clig.dev for design conventions, and their Output section mentions:Additionally, when discussing where color should be disabled, one of the points is:
That implies it's good practice to print to
stderr, even when there's no TTY.A good example would be checking the logs when using
ghin GitHub Actions and other continuous integration.However, I also agree that breaking changes are bad and I'm not sure if this is worth one. It could be worth considering the
CIenvironment variable, which is automatically set in GitHub Actions and other tools. OrGH_CIto be cautious of breaking changes. For example, whenCIis set to any value and there is no TTY, I'd suggest:stderras if there were a TTY, including color. Color could still be disabled withNO_COLORCLICOLOR, etc.stdoutmachine readable like it currently is when there is no TTYGMNGeoffrey commentedon Oct 23, 2025
I came here starting with similar issues of weirdly different behavior of
ghcommands. This isn't limited togh pr list; I also encountered it withgh repo view. It also changes not only the output, but also the exit code.I understand it's nice to provide the most useful interface for a given use case, but I really dislike the pattern where a tool does different things depending on how you look at it (colored output is perhaps an exception). It seems totally backwards to me that in scripting it would try to guess what repo to use: I want my scripts to be debuggable and explicit and this is the exact opposite. If you want to have something by-default interactive, then I think the better way to do it is to have the tool fail in the non-interactive case and require that you explicitly pass a flag (e.g.
-yforaptandpip).Probably the ship has sailed, but I find myself hitting these kinds of issues every time I try to use
ghfor scripting. It massively increases the complexity of the tool and creates all kinds of weird footguns.