Confirmed at runtime, not just from reading the source. I put a logging shim earlier on PATH than the real gh and drove the shipped v0.0.33 Linux AppImage through the normal UI action. The shim intercepted pr create so nothing could be created; every other subcommand was passed through untouched.
Captured pr create invocation (branch and repo names genericized):
| 1 | cwd=/path/to/fork-clone |
| 2 | argv: [pr] [create] [--base] [main] [--head] [<branch>] [--title] [...] [--body-file] [/tmp/t3code-pr-body-<pid>-<uuid>.md] |
--head is unqualified. The branch exists only on origin (the fork); gh's resolved base repo is upstream via remote.upstream.gh-resolved=base. So gh is asked for <branch> in a repo that does not have it, and exits 1 — surfacing as the generic createChangeRequest error.
Note --base main in the same command: that value came from getDefaultBranch → gh repo view --json defaultBranchRef, which honours gh-resolved and therefore reports the upstream repo's default branch. Base is resolved through gh, head is assumed to live on origin. Both halves of the asymmetry are visible in that single argv.
The predicate flipping, observed live. Same repo, same branch, same running process — only the branch's tracking remote changed between these two background pr list probes:
| 1 | argv: [pr] [list] [--head] [upstream:<branch>] ... # branch tracked upstream -> qualified |
| 2 | argv: [pr] [list] [--head] [<branch>] ... # branch tracked origin -> bare |
That is isCrossRepository in resolveBranchHeadContext doing exactly what the report describes. When the branch tracks origin and origin is the fork, the comparison is fork-vs-fork, the predicate is false, and the correctly-computed ownerHeadSelector (<you>:<branch>) is discarded in favour of the bare headBranch. Nothing about whether the branch exists on any remote enters into it — the selector is decided purely from branch.<name>.remote and the remote URLs, so this reproduces from git config alone.
For contrast, gh resolves the same worktree correctly when it is allowed to infer the head itself:
| 1 | $ gh pr create --base main --title t --body-file /dev/null --dry-run |
| 2 | head: <you>:<branch> |
| 3 | |
| 4 | $ gh pr create --base main --head <branch> --title t --body-file /dev/null --dry-run |
| 5 | head: <branch> # what T3 Code sends |
The explicit --head is what suppresses that inference.
One incidental observation from the same log, unrelated to this bug and already covered by #3581: the shim recorded ~450 gh pr list invocations in roughly ten idle minutes, at close to one per second.