Skip to content

[pull] trunk from cli:trunk - #14

Open
pull[bot] wants to merge 1409 commits into
Moshbbab:trunkfrom
cli:trunk
Open

pull[bot] wants to merge 1409 commits into
Moshbbab:trunkfrom
cli:trunk

Conversation

@pull

@pull pull Bot commented Jun 1, 2026 •

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

@pull pull Bot locked and limited conversation to collaborators Jun 1, 2026
@pull pull Bot added ⤵️ pull merge-conflict Resolve conflicts manually labels Jun 1, 2026
BagToad and others added 27 commits September 29, 2026 20:18
Harden filesystem writes against symlinks
A Markdown file's relative links are written relative to the file, but a
--body-file had to reference attached images relative to the working
directory instead. References in a --body-file now resolve against the
file's directory first and the working directory second, so references
that matched before still match. --body and standard input still resolve
against the working directory.

The directory is a parameter of UploadAndAttach, so all six --attach
commands resolve references the same way.
A command that takes several Markdown files, such as the planned
`gh issue artifact create`, needs --attach to work across all of them.
UploadAndAttachDocuments uploads each file once, in order, stopping at
the first failure, and gives every document that references a file the
same URL. Each document resolves its references against its own
directory and gets its own result, so the command can decide whether
to save it.

With several documents, a file that no document references has no body
to be appended to. It is refused before anything uploads, with an
error that names it. With one document, unreferenced files are still
appended, and UploadAndAttach now runs through the same code with one
document.
Commands reach the issue artifacts REST API through the ArtifactClient
interface, so their tests can use the generated moq mock, as gh
discussion does.

List asks for pages of up to 100 artifacts until one comes back short.
Every request uses the same page size, so page numbers stay aligned when
the limit is above 100.
gh issue artifact is a new command group, in preview, for the Markdown
documents and links attached to an issue. It starts with list.

gh treats generic and plan artifacts alike as documents, so --type
generic and --type plan list both, merged in number order. Without a
total count from the API, the header counts the artifacts shown.
Get returns one artifact with the versions the API returns alongside it,
newest first, for gh issue artifact view. Only a single artifact has an
edit history, since list responses don't include one, so its --json
fields add versions to the artifact fields.
view shows an artifact like gh issue view: its name and issue, who
created and last edited it, its body rendered as Markdown or a link's
URL, and its edit history. --version shows an earlier version from that
history. Piped output has key lines, then the body, and --json adds the
versions field.

Artifacts have no web page of their own, so the footer links to the
issue and --web only opens link artifacts.

The shared package gains three checks later commands need too: the
artifact number argument, refusing types this version of gh doesn't
know, and the strict http(s) URL check for links.
Delete removes one artifact from an issue, for gh issue artifact delete.
The API can't restore a deleted artifact.
delete asks before deleting, since a deleted artifact can't be restored.
It looks up every number first, so the prompt names each artifact, and
without --yes a number that can't be found stops the command before
anything is deleted. --yes skips the prompt and is required when gh
can't prompt, like gh label delete.

Commands that act on several artifacts report one line per artifact.
The shared package gains those result lines, so each such command prints
the same terminal lines and the same five-column piped lines.
download --version follows the same rules as view --version, so both
commands use one lookup.
Download writes each artifact to <number>-<name>.md, or .url for a link,
never through a symlink. The shared result lines gain a skipped line for
--skip-existing.
Create posts an artifact's type, name and body to an issue and returns
the new artifact.

UploadTarget is the issue lookup for commands that take --attach. Like
IsPullRequest it makes one lookup, which also returns the repository ID
and the viewer's permission that an upload needs.
gh issue artifact create names an artifact with <file>#<name>, parsed
exactly like --attach's alt text: a path that exists wins, then the
longest existing path before a #. One exported parser keeps # meaning
the same in both.
create makes one artifact for each file or URL argument, in argument
order. Text files become documents named after the file, or after
#<name>; URLs and .url Internet Shortcut files become links, and a URL
is named after its host. --body and --body-file create one artifact,
with --name, and --body-file - is the only way standard input is read.
--type plan saves documents as plans.

Every file is read and checked before any request, so a binary or empty
file, or a shortcut without an http(s) URL, fails the whole command.
Names gh takes from file names drop the characters artifact names can't
contain.

--attach uploads each file once for every document that references it,
through UploadAndAttachDocuments. A document is saved when any of its
files uploaded, since uploads can't be undone, and its line gives the
upload error as the reason. One whose files all failed isn't saved.

The shared package gains what edit will need too: reading new content
with the binary and empty checks, reading .url files, the lookup that
also serves --attach, and a result line for an artifact saved despite
a failure.
Update saves a new version of one artifact. Its PATCH request sends only
the name or body it changes, since the API keeps any field a request
leaves out. An artifact's type can't change.
gh issue artifact edit needs the uploader create builds: the one issue
lookup that refuses a pull request and returns the repository ID and
permission an upload needs, then the checks attachments.NewUploader
makes. shared.NewUploader now builds it for both.
edit renames an artifact with --name, replaces its content with --body
or --body-file, or both, and saves a new version. --body-file - is the
only way standard input is read, and a file never renames the artifact.

The type never changes, so it is enforced before anything is uploaded or
saved. A link's new content must be an http(s) URL: a .url file is read
as an Internet Shortcut, and anything else must be a bare URL, with its
surrounding whitespace trimmed. A document never takes a .url file, and
a link can't take --attach. Types gh doesn't know are refused.

--attach on its own appends the files to the current body, like
gh issue comment --edit-last --attach. With new content, it points the
content's references at the uploads. The artifact is saved when any file
uploaded, since uploads can't be undone, and the upload error is the
reason on its line.

edit reports its artifact on one result line in the format create,
delete and download share. A failed lookup or save is a failed line,
like in delete.
The gh skill had no guidance for the gh issue artifact commands, and its
--attach section listed only the six earlier commands. Add an issue
artifacts section with each command's usage and the rules that apply
across them, and note how --attach differs for gh issue artifact create
and edit.
Resolve go.mod/go.sum conflicts by keeping trunk's dependency versions
and re-pinning go-gh to v2.16.2-0.20260914233729-2ce792192e43. The old
pseudo-version was v2.16.1-0.*, which sorts below the v2.16.1 release.
The cli/oauth pin is unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
v0.0.27 eagerly built a 2.2MB width lookup table in init(),
adding ~12ms to every gh invocation. v0.0.30 builds it lazily.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Bump go-runewidth to v0.0.30 for faster startup
gh issue artifact edit --restore-version saves nothing when an artifact
already matches the version. PrintUnchanged reports that: in a terminal,
a warning on stderr like gh issue close on a closed issue, so stdout
stays empty, and when piped, a skipped line with the reason. PrintSkip
doesn't fit, since its terminal line goes to stdout.
--restore-version saves an earlier version's name and body as a new
version. The version comes from the edit history that the artifact's
lookup already returns, so a restore makes no extra request, and a
version that isn't there fails before anything is saved. The name and
body are sent as returned, so the server checks them like any other
edit.

Every save adds a version to the edit history, even when nothing
changed. So nothing is saved when the artifact already matches the
version, whether that's the current version or an older one with the
same name and body.

The flag takes the same values as view --version, and can't be combined
with --name, --body, --body-file or --attach.
Add the flag to the gh issue artifact edit entry, and the restored
status to the piped result lines.
Signed-off-by: reginaldalfret <reginaldalfret@gmail.com>
…ix-shell

docs: recommend nix-shell and declarative config for Nix/NixOS
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
babakks and others added 30 commits October 21, 2026 12:45
FuzzyIn chained truncating integer divisions, so a duration a hair
under a round boundary read one unit short: a 6 month refresh token
reported "in about 5 months" a second after issuance. Round each unit
to the nearest whole value instead, and report a value that rounds up
to the next unit's threshold in that larger unit so it never reads
"in about 60 minutes" or "in about 12 months".

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac178b5a-5b62-49bd-9e83-4796c7ddec46
Both successful expiring-token text fixtures set a refresh token
expiry, so the guard that omits the refresh token expiry line was only
reached through the rejected-token path. Add a fixed-clock case that
asserts the access token annotation is shown and the refresh token
expiry line is absent, and a --json case that asserts
refreshTokenExpiresAt is omitted while tokenExpiresAt is kept.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac178b5a-5b62-49bd-9e83-4796c7ddec46
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac178b5a-5b62-49bd-9e83-4796c7ddec46
Refreshable tokens: show relative time-to-expiry in `gh auth status`
Add an internal/debug package that exposes Config, which returns a
DebugConfig holding internal debugging overrides as Option values, and
Enabled, which reports whether gh was built with the ghdebug build tag.
In ghdebug builds Config reads GH_INTERNAL_AT_EXPIRES_IN and
GH_INTERNAL_RT_EXPIRES_IN as token lifetimes in seconds and
GH_INTERNAL_DEBUG_REFRESH_TOKEN as a boolean, leaves unset overrides as
None, and panics on invalid values. In regular builds every override is
always None, so the overrides cannot be enabled in released binaries.
The tag is prefixed with gh to avoid colliding with a generic debug tag.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac178b5a-5b62-49bd-9e83-4796c7ddec46
Add "make debug", which builds bin/gh like "make" but with the ghdebug
build tag so the internal debug overrides are available. It removes
bin/gh first because script/build skips rebuilding when no source file
has changed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac178b5a-5b62-49bd-9e83-4796c7ddec46
Append "[debug build]" to the first line of gh version and gh --version
when gh is built with the ghdebug build tag, so a debug binary is easy
to tell apart from a regular one. Regular builds are unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac178b5a-5b62-49bd-9e83-4796c7ddec46
Replace the ad hoc os.Getenv testing hacks with the build-gated
internal/debug configuration. The access and refresh token lifetime
overrides in internal/config and the verbose refresh logging switch in
the factory now come from GH_INTERNAL_AT_EXPIRES_IN,
GH_INTERNAL_RT_EXPIRES_IN, and GH_INTERNAL_DEBUG_REFRESH_TOKEN, and have
no effect in builds without the ghdebug tag.

Update the refreshable-token e2e scripts to use the new variable names,
rename their own settings to GH_INTERNAL_E2E_BIN, GH_INTERNAL_E2E_HOST,
and GH_INTERNAL_E2E_ALLOW_VSCODE, and refuse to run unless gh version
reports a debug build, pointing at "make debug" otherwise.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac178b5a-5b62-49bd-9e83-4796c7ddec46
Check that gh auth token prints the expiry warning on stderr whenever it
prints a stored expiring token, and that the warning is absent when no
stored expiring token is printed, such as with GH_TOKEN. Also run gh
auth refresh both with and without --expiring and assert that each
receives an expiring refreshable token.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac178b5a-5b62-49bd-9e83-4796c7ddec46
script/build skipped rebuilding bin/gh whenever no source file was newer than the binary, regardless of build tags. A plain `make` straight after `make debug` therefore kept the debug binary.

Compare the tags recorded in the existing binary's build info with GO_BUILDTAGS and rebuild when they differ. This also lets `make debug` drop its `rm -f bin/gh` workaround.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac178b5a-5b62-49bd-9e83-4796c7ddec46
Move the environment parsing into an unexported configFromEnv in the untagged config.go. The ghdebug implementation of Config now just calls it, while the release implementation still returns an empty DebugConfig.

This lets the regular test run cover all parsing and panic cases on every OS without the ghdebug tag. The tagged test only checks that Config reads the real environment. Release builds never reference configFromEnv, so the linker still drops the parser and the variable names from them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac178b5a-5b62-49bd-9e83-4796c7ddec46
The ghdebug implementation of internal/debug is not compiled by the regular test run or build, so it could silently stop compiling. Build ./cmd/gh with the tag on the Ubuntu runner to catch that.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac178b5a-5b62-49bd-9e83-4796c7ddec46
Refreshable tokens: build-gated debug overrides for testing token expiry
cli/oauth v1.3.0 exposes the access and refresh token expiry fields as
time.Duration values, so convert them when building the stored credential.
Refresh now keeps the previous refresh token when the server omits a new
one, which the godoc and the omitted refresh token test reflect.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac178b5a-5b62-49bd-9e83-4796c7ddec46
Lip Gloss v2 includes borders in Width, which made the bordered left
column one cell narrower and wrapped truncated rows. Widen it by one
cell and restore the original test expectations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac178b5a-5b62-49bd-9e83-4796c7ddec46
Refreshable tokens: bump cli/oauth to v1.3.0 and fix `gh status` column width
Teach agents how gh reports and refreshes expiring tokens, which hosts
support them, why `gh auth token` output must not be cached, and how to
handle a rejected refresh token.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac178b5a-5b62-49bd-9e83-4796c7ddec46
Clarify behavior of `gh auth status --json` regarding token refresh and expiration.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Document expiring tokens in the gh skill
Keep the pre-install disclaimer and present post-install review guidance without a second warning. Preserve pinned preview commands and cover local and remote output in TTY and non-TTY modes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c5ee8a8a-f0e8-432e-8ebb-62ff72712b41
Avoid repeating the skill installation safety warning
Refresh the pinned revision and module checksums.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 437a79bf-e43d-4730-9528-13e90c8bcc9a
Update github.com/digitorus/pkcs7 dependency
Copilot-Session: 9524ec94-45f1-4a7a-aff5-fd85cda6ff2b
Print a one line hint on stderr pointing users to --expiring and the
changelog post (https://gh.io/gh-expiring-tokens). The hint only shows
when stderr is a TTY, neither --expiring nor expiring_credentials is
set, and the host is not GitHub Enterprise Server.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac178b5a-5b62-49bd-9e83-4796c7ddec46
feat(gh auth login): hint at `--expiring` when no preference is set
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Allow installation tokens to attach files
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 664e416d-0c36-420b-a93c-dbfd8ff83769
…okens

Defer `--attach` credential checks to the server
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

⤵️ pull merge-conflict Resolve conflicts manually

Projects

None yet

Development

Successfully merging this pull request may close these issues.