Windows PowerShell parses git @{u} upstream shorthand as a hashtable literal

Open 💬 2 comments Opened Jun 28, 2026 by Efs-O

Bug: git rev-parse ... @{u} fails on Windows because PowerShell parses @{u} as a hashtable literal

Summary

On Windows with PowerShell as the shell, Codex's tool router runs

git rev-parse --abbrev-ref --symbolic-full-name @{u}

by handing it to PowerShell as a command string. PowerShell sees @{ and tries
to parse @{u} as a hashtable literal, which fails before git is ever
invoked:

At line:2 char:52
+ git rev-parse --abbrev-ref --symbolic-full-name @{u}
+                                                    ~
Missing '=' operator after key in hash literal.
The hash literal was incomplete.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : MissingEqualsInHashLiteral

The command exits 1 and the upstream-branch lookup never runs. In git, @{u}
(a.k.a. @{upstream}) is the revision shorthand for "the upstream of the current
branch" - a perfectly valid git argument that PowerShell mangles because @{...}
is hashtable syntax in PowerShell.

Environment

  • OS: Windows 10 Pro (10.0.19045)
  • Shell: Windows PowerShell 5.1 (5.1.19041.7417) - also reproduces on

PowerShell 7, since @{ is hashtable syntax in all PowerShell editions

  • Codex CLI: 0.142.2
  • ChatGPT VS Code extension: 26.623.42026 (win32-x64)

Actual log line (from the Codex output channel)

[error] [CodexMcpConnection] cli: message="... ERROR codex_core::tools::router:
error=Exit code: 1
Wall time: 0.4 seconds
Output:
At line:2 char:52
+ git rev-parse --abbrev-ref --symbolic-full-name @{u}
+                                                    ~
Missing '=' operator after key in hash literal.
At line:2 char:52
+ git rev-parse --abbrev-ref --symbolic-full-name @{u}
+                                                    ~
The hash literal was incomplete.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : MissingEqualsInHashLiteral"

Steps to reproduce

  1. On Windows, with PowerShell as the active shell, open a git repository whose

current branch has an upstream configured.

  1. Use Codex in a flow that triggers its branch/upstream detection (it runs

git rev-parse --abbrev-ref --symbolic-full-name @{u} internally; observed via
turn-diff / branch-status capture).

  1. Open the Codex output channel and observe the ParserError above.

Minimal standalone repro of the underlying cause (no Codex needed):

# FAILS - PowerShell parses @{u} as a hashtable literal:
git rev-parse --abbrev-ref --symbolic-full-name @{u}

# WORKS - quoting stops PowerShell from parsing the braces:
git rev-parse --abbrev-ref --symbolic-full-name '@{u}'

# WORKS - stop-parsing token passes the rest verbatim:
git --% rev-parse --abbrev-ref --symbolic-full-name @{u}

Expected behavior

git rev-parse --abbrev-ref --symbolic-full-name @{u} should run and return the
upstream branch (e.g. origin/main), regardless of which shell Codex uses on
Windows.

Root cause

Codex builds the git invocation as a PowerShell command string, so the
@{u} argument is subject to PowerShell's parser. @{ ... } is PowerShell's
hashtable-literal syntax; @{u} has a key with no =, so PowerShell raises a
ParserError (MissingEqualsInHashLiteral) and never spawns git. This is a
shell-quoting / argument-passing issue specific to the Windows PowerShell path -
on bash/zsh the same string is passed through to git unharmed.

Suggested fixes (any one resolves it)

  1. Pass argv directly, bypassing shell parsing. Spawn git with an argument

array (e.g. Start-Process/CreateProcess-style argv, or git.exe invoked
with discrete args) instead of composing one PowerShell command string. This
is the most robust fix and avoids an entire class of PowerShell-quoting bugs.

  1. Quote the revision argument when targeting PowerShell:

... --symbolic-full-name '@{u}'.

  1. Use the PowerShell stop-parsing token --% before the git args so the

remainder is passed verbatim: git --% rev-parse ... @{u}.

Option 1 is preferred because other git revision/argument forms (HEAD@{0},
@{push}, paths/globs with special chars) can hit similar PowerShell-parsing
pitfalls.

Impact / severity

Low severity, but persistent and Windows-specific:

  • Affects only the Windows + PowerShell path; macOS/Linux are unaffected

(which is likely why it has survived frequent extension updates - the
PowerShell path is under-tested).

  • It fails a non-critical background helper (upstream-branch / turn-diff

capture), so the user's primary command still works; the failure surfaces only
as a log error, generating little user-facing signal.

  • Net effect: noisy, repeated ParserError in the Codex output channel on every

affected turn, and broken upstream-branch detection for Windows PowerShell
users.

View original on GitHub ↗

2 Comments

Efs-O · 2 months ago

Proposed fix: avoid routing this Git invocation through PowerShell as an unquoted command string. Spawn git with discrete argv instead:

[rev-parse, --abbrev-ref, --symbolic-full-name, @{u}]

That lets Git receive @{u} literally and also avoids similar PowerShell parsing problems for related Git revspecs like HEAD@{0} or @{push}.

If this path must remain shell-string based for now, quoting the revspec also fixes the immediate bug on PowerShell:

git rev-parse --abbrev-ref --symbolic-full-name '@{u}'
Nicolas0315 · 2 months ago

I pushed a candidate fix to my fork:

What changed:

  • PowerShell command preparation now quotes unquoted git revspec tokens containing @{...} when they appear inside a git command before execution.
  • This covers @{u}, @{upstream}, @{push}, and reflog forms like HEAD@{0}.
  • It leaves already quoted revspecs alone and avoids non-git PowerShell hashtables such as @{u=1}.

Local validation:

  • just fmt
  • just test -p codex-shell-command (205/205)
  • git diff --check

SSH validation:

  • Nicolas-MacBook-Pro.local: git diff --check; cargo test -p codex-shell-command fallback passed (145/145; cargo-nextest unavailable there).
  • nicolas2025 Windows: git diff --check; cargo test -p codex-shell-command --lib powershell::tests:: passed (15/15).
  • rtx4090 Windows: commit c9ac0fd32; git diff --check; scoped cargo test -p codex-shell-command ... powershell::tests:: completed successfully. Full just test -p codex-shell-command was blocked by repeated git ls-remote https://github.com/openai/plugins.git HEAD network hangs on that host, so I killed only the orphaned git processes from that validation run.
  • nicolas2025 WSL: git diff --check passed; Rust build is blocked by missing pkg-config / OpenSSL development package (openssl-sys cannot find OpenSSL).
  • RTX5060Ti Windows: git diff --check passed; Rust build is blocked by the host MSVC linker setup (link.exe treats Rust object files as extra operands).

PR create note:

  • I tried to open the upstream PR, but GitHub rejected it with GraphQL: Nicolas0315 does not have the correct permissions to execute CreatePullRequest (createPullRequest).