Skip to content

feat: add experimental git credential helper for external auth - #30914

Draft
matifali wants to merge 1 commit into
mainfrom
matifali/git-credential-helper
Draft

matifali wants to merge 1 commit into
mainfrom
matifali/git-credential-helper

Conversation

@matifali

@matifali matifali commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

Warning

Experimental prototype. Opening it to discuss the approach in #30913, not to merge yet.

Closes #30913

The agent gives HTTPS Git its external auth tokens through GIT_ASKPASS. Some programs turn off askpass in the Git processes they spawn but keep credential helpers. Claude Code does this when it clones plugin marketplaces, so private marketplaces fail in workspaces even though plain git works.

This adds a Git credential helper alongside askpass.

Changes

  • coder gitcredential <get|store|erase> (hidden): implements the Git credential helper protocol.
    • get makes one call to the agent external auth endpoint with match=<protocol>://<host>[/<path>], the same URL shape askpass sends, so it resolves the same provider.
    • It prints username, password (falling back to the username, like askpass does for GitHub), and password_expiry_utc when the token has an expiry.
    • It prints nothing when no provider serves the host (404), so Git falls through.
    • When the provider isn't linked, it prints the auth URL to stderr and returns no credentials. It never long-polls. GIT_ASKPASS still handles the interactive login.
    • store and erase do nothing.
  • Agent registers it as credential.helper through GIT_CONFIG_COUNT, GIT_CONFIG_KEY_<n>, and GIT_CONFIG_VALUE_<n>. The entry is appended after any entries the template, user secrets, or the inherited environment already set, and skipped if it's already there. Git reads these after config files, so the user's own helpers are tried first.
  • --git-credential-helper / CODER_AGENT_GIT_CREDENTIAL_HELPER (default true) turns it off.
  • Docs: a "Git credential helper" section in docs/admin/external-auth.md, plus an updated askpass note in the template external auth docs.

Testing

  • Unit tests for request parsing, coder gitcredential (token-as-username, username and password, no provider, not linked, ignored operations), env appending, and helper quoting.
  • On a dogfood workspace with GitHub external auth, using a binary built from this branch, registered the way the agent does (!'<path>/coder' gitcredential):
    • gitcredential get for github.com returned the token and password_expiry_utc. For example.com, it returned nothing with exit code 0.
    • git ls-remote on a private repository worked with askpass disabled the way Claude Code disables it (GIT_ASKPASS= and core.askPass=).
    • claude plugin marketplace add https://github.com/<org>/<private-repo>.git (Claude Code 2.1.302) cloned successfully. Without the helper, it fails with unable to get password from user.
  • Benchmark (hyperfine, 30 runs): a full git ls-remote takes 800ms with askpass and 597ms with the helper, or 282ms with git credential-cache in front of it. The credential lookup alone goes from 508ms (two calls to coderd) to 287ms (one call).
  • Agent: register a Git credential helper so programs that disable GIT_ASKPASS still get external auth tokens #30913 has the earlier evidence: Claude Code appending to GIT_CONFIG_*, helper order, the managed-settings startup install, the provider-not-linked case, and timings.

Open questions

  • Default on (as here) or opt-in?
  • Hosts that no provider serves cost two round trips (the helper misses, then askpass asks again).
  • Is the !'<path>' gitcredential form OK on Windows? It uses the same UNIX-style path as GIT_SSH_COMMAND, but I haven't tested it.

@matifali matifali added the experimental Changes that might not necessarily be merged, until its approved to proceed with. label Oct 17, 2026
@matifali matifali self-assigned this Oct 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Docs preview

Check off each page once it's been reviewed. If a page changes in a later push, its checkbox clears automatically so it gets a fresh look. Pages not yet wired into the docs navigation aren't listed here.

@matifali
matifali force-pushed the matifali/git-credential-helper branch from 53d71a8 to cb8db3e Compare October 17, 2026 12:24
@matifali

Copy link
Copy Markdown
Member Author

Benchmark: GIT_ASKPASS vs coder gitcredential

I used hyperfine (3 warmups, 30 runs) to measure git ls-remote <repo> HEAD against github.com with each auth mechanism. The coder binary was built from this branch (cb8db3e).

Environment: dogfood workspace (coder template, Pittsburgh), Git 2.53.0, GitHub external auth linked. The host was busy (load average 10 to 14), so treat the absolute numbers as rough. The differences between cases are the useful part.

Full Git operation

Case Mean ± σ (ms) Median Min Max Auth overhead vs. public repo
Public repo, no auth (network floor) 189 ± 36 176 146 246 n/a
Private repo, GIT_ASKPASS (today) 800 ± 39 798 730 894 +611
Private repo, coder gitcredential, askpass disabled 597 ± 32 597 547 675 +408
Private repo, git credential-cache in front of coder gitcredential 282 ± 27 275 250 346 +93

Credential lookup only

Case Mean ± σ (ms) Median Min Max
GIT_ASKPASS: username + password prompts (2 calls to coderd) 508 ± 24 504 458 559
coder gitcredential get (1 call to coderd) 287 ± 31 282 244 401

Takeaways

  • Git calls askpass twice per authentication, once for the username and once for the password, and each call reaches coderd. The helper returns both from one call, so lookup time drops by about 44% and a full ls-remote gets about 200ms faster.
  • The helper returns password_expiry_utc, so putting git credential-cache in front of it is safe across token rotation. That cuts auth overhead from about 600ms to about 90ms. The cache isn't part of this PR; it's a possible follow-up for templates or the agent.
  • In every case the time includes starting the ~300MB coder binary and the detectGitRef calls that askpass already makes.
Benchmark script
#!/usr/bin/env bash
# Benchmarks Git HTTPS auth in a Coder workspace: GIT_ASKPASS vs the
# `coder gitcredential` helper. Usage: bench.sh <private-https-repo-url>
set -euo pipefail
PRIVATE=${1:?private repo URL}
PUBLIC=https://github.com/coder/coder
DIR=$(cd "$(dirname "$0")" && pwd)
HELPER="!'$DIR/coder-dev' gitcredential"
mkdir -p "$DIR/cases" "$DIR/results" "$DIR/cache" && chmod 700 "$DIR/cache"
export GIT_TERMINAL_PROMPT=0

cat > "$DIR/cases/1-public-no-auth" <<S
git -c credential.helper= ls-remote $PUBLIC HEAD
S
cat > "$DIR/cases/2-askpass" <<S
git -c credential.helper= ls-remote $PRIVATE HEAD
S
cat > "$DIR/cases/3-gitcredential" <<S
GIT_ASKPASS= git -c core.askPass= -c credential.helper= -c credential.helper="$HELPER" ls-remote $PRIVATE HEAD
S
cat > "$DIR/cases/4-gitcredential-cached" <<S
GIT_ASKPASS= git -c core.askPass= -c credential.helper= -c credential.helper="cache --timeout=3600 --socket=$DIR/cache/sock" -c credential.helper="$HELPER" ls-remote $PRIVATE HEAD
S
cat > "$DIR/cases/5-askpass-auth-only" <<'S'
GIT_PREFIX= "$GIT_ASKPASS" "Username for 'https://github.com': " >/dev/null && GIT_PREFIX= "$GIT_ASKPASS" "Password for 'https://x@github.com': " >/dev/null
S
cat > "$DIR/cases/6-gitcredential-auth-only" <<S
printf 'protocol=https\nhost=github.com\n\n' | $DIR/coder-dev gitcredential get >/dev/null
S

# Sanity: every case must succeed before timing.
for c in "$DIR"/cases/*; do bash "$c" >/dev/null 2>"$DIR/results/sanity.err" && [ ! -s "$DIR/results/sanity.err" ] || { cat "$DIR/results/sanity.err"; echo "case failed: $(basename "$c")"; exit 1; }; done

args=()
for c in "$DIR"/cases/*; do args+=(-n "$(basename "$c")" "bash $c"); done
hyperfine --warmup 3 --runs 30 --export-json "$DIR/results/bench.json" --export-markdown "$DIR/results/bench.md" "${args[@]}"

Run with bench.sh https://github.com/<org>/<private-repo>. coder-dev is go build -o coder-dev ./cmd/coder from this branch, placed next to the script.

The agent provides external auth tokens to Git through GIT_ASKPASS. Some
programs disable askpass for the Git processes they spawn while keeping
credential helpers enabled, so HTTPS Git fails for them in workspaces.
Claude Code does this when it clones plugin marketplaces.

Add a hidden `coder gitcredential` command that implements the Git
credential helper protocol, and have the agent register it through
GIT_CONFIG_COUNT/GIT_CONFIG_KEY_<n>/GIT_CONFIG_VALUE_<n>, appended after
any entries that are already set. The helper resolves the provider like
askpass, never long-polls, and returns password_expiry_utc when known.
GIT_ASKPASS stays in place for the interactive login flow.

The agent flag --git-credential-helper (CODER_AGENT_GIT_CREDENTIAL_HELPER)
turns it off.
@matifali
matifali force-pushed the matifali/git-credential-helper branch from cb8db3e to 8351967 Compare October 17, 2026 17:27

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

experimental Changes that might not necessarily be merged, until its approved to proceed with.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Agent: register a Git credential helper so programs that disable GIT_ASKPASS still get external auth tokens

1 participant