Repository navigation
Conversation
Setup read repository identity from `gh repo view`, which answers "what is the GitHub CLI's base repository here" rather than "which repository does this checkout push to". The two differ exactly where it matters: in a fork checkout that also has an upstream remote, `gh` prefers the parent, so LOCAL_REPO became the upstream, PUSH_REMOTE pointed at it, ROLE stayed `owner`, and the fork disappeared from every pull-request workflow. With no default set and several remotes qualifying, `gh` instead stops to ask which base repository to use, so an unattended run turned into a prompt. Add lib/github/scripts/repo-identity.sh as the single identity resolver for the pull-request and issue workflows. It reads every remote fetch and push URL, pins one host, validates each candidate through host-pinned REST metadata, and selects the write target by evidence: a fork the authenticated account can actually push to wins, so a remote for somebody else's fork never becomes the push target; several writable forks resolve only when exactly one belongs to that account; the base repository wins only when the account can push to it; and with nothing writable the resolver fails, naming the base repository and every fork remote it rejected. Setup consumes that result and no longer derives the host, fork state, or default branch itself. Delegate `issue-context.sh repository` to the same resolver so issue and pull-request identity cannot drift, and keep its read-only preference rule: without push evidence a fork remote still identifies the checkout. A fork checkout now pushes to its fork and opens the pull request against the parent. A checkout with no writable repository stops during setup with the remedy named, instead of continuing until Git refuses the push.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7465e68680
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The resolver checked ownership only when several writable forks were present. A single writable fork was selected on push permission alone, so an account that is a collaborator on somebody else's fork — a maintainer with write access to a teammate's fork and a writable upstream remote — resolved that fork as LOCAL_REPO and would have published to the wrong repository, against the documented guarantee that a remote for somebody else's fork never becomes the push target. Resolve the authenticated login whenever any writable fork is present and require ownership in every case, not only in the ambiguous one. A writable fork owned by another account is now rejected: the base repository wins when the account can push to it, and otherwise the failure names the rejected fork and the account that does not own it. More than one owned writable fork stays an error rather than a guess.
lyfne123
approved these changes
Aug 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Repository identity for the pull-request workflows came from
gh repo view,which resolves the GitHub CLI's base repository rather than the repository a
checkout pushes to. In a fork checkout that also has an upstream remote
ghprefers the parent, so
LOCAL_REPObecame the upstream,PUSH_REMOTEpointedat it,
ROLEstayedowner, and the fork disappeared from publication,lookup, and repair alike; when several remotes qualify and no default is set,
ghstops to ask which base repository to use, which turns an unattended runinto a prompt. Identity now comes from the Git remotes through one resolver
shared by the pull-request and issue workflows: a fork checkout pushes to its
fork and targets the parent, and a checkout with nothing writable stops during
setup with the remedy named instead of continuing until Git refuses the push.
Changes
lib/github/scripts/repo-identity.sh: new resolver.resolve [--require-push]reads every remote fetch and push URL, pins one host,validates each candidate through host-pinned REST metadata, and reports
github_host,local_repo,base_repo,is_fork,local_can_push, anddefault_branch. With--require-pushthe write target is chosen byevidence: a fork the authenticated account both owns and can push to wins.
Write access is not ownership — an account can be a collaborator on somebody
else's fork — so a fork owned by another account is rejected whether it is
the only writable one or one of several. More than one owned writable fork is
an error rather than a guess, the base repository wins only when the account
can push to it, and with nothing writable the resolver fails, naming the base
repository and every fork remote it rejected.
lib/github/setup.md: section 2 consumes that resolver with--require-pushinstead of
gh repo view, and no longer derives the host, fork state, ordefault branch itself.
LOCAL_REPOis now the verified writable repository,so section 3 selects the fork remote as
PUSH_REMOTEwhen a fork exists.lib/github/scripts/issue-context.sh:repositorydelegates to the sameresolver and reshapes its output to the unchanged
github_host,local_repo,issue_repo,default_branchcontract, so issue andpull-request identity cannot drift. Without push evidence the read-only rule
is unchanged: a fork remote still identifies the checkout.
lib/github/issue-context.md,lib/github/common-issues.md: document thedelegation and add diagnostics for a checkout with no writable repository and
for several writable fork remotes.
tests/test_repo_identity.py: 15 executable cases over a fakeghand realGit remotes — fork beside upstream, upstream-only, unwritable upstream, an
unwritable foreign fork remote, a sole writable fork owned by another
account both with and without a writable base, nothing writable, two owned
writable forks, one owned among several, none owned among several, read-only
resolution, unrelated hosts, unrelated repositories, no remotes, and usage
errors.
tests/test_portability.py:setup.mdmay now consumeREPO_IDENTITY_HELPER;new assertions pin the resolver's presence and executability, the
--require-pushcall, the documented selection rule, the issue-sidedelegation, and a repository-wide invariant that no deployable shell calls
gh repo view.No interface changes for callers of the skills:
setup.mdproduces the samecontext variables, and
issue-context.sh repositoryreturns the same JSONshape. Consumers pick this up when the plugin version they have installed is
updated; this change does not bump the plugin version.
Review feedback addressed in
f037c85: ownership is verified in the single-writable-fork case too, so write access to somebody else's fork never selects it as the push target.Verification
python -m ruff check tests: 0, All checks passedpython -m ruff format --check tests: 0, 15 files already formattedpython -m pyright: 0, 0 errors, 0 warnings, 0 informationsgit ls-files -z -- '*.sh' | xargs -0 -r -n 1 bash -n: 0python -m unittest discover -s tests: 0, 230 tests OKthe pushed commit, and passed both times.
repo-identity.sh resolve --require-pushin three live checkouts: a forkcheckout with an upstream remote resolved to the fork with the parent as
base, a second fork checkout did the same, and a checkout with only an
upstream remote resolved to that repository with
is_forkfalse.