Repository navigation
Conversation
Docs previewCheck 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. |
53d71a8 to
cb8db3e
Compare
Benchmark: GIT_ASKPASS vs
|
| 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-remotegets about 200ms faster. - The helper returns
password_expiry_utc, so puttinggit credential-cachein 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
coderbinary and thedetectGitRefcalls 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.
cb8db3e to
8351967
Compare
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 plaingitworks.This adds a Git credential helper alongside askpass.
Changes
coder gitcredential <get|store|erase>(hidden): implements the Git credential helper protocol.getmakes one call to the agent external auth endpoint withmatch=<protocol>://<host>[/<path>], the same URL shape askpass sends, so it resolves the same provider.username,password(falling back to the username, like askpass does for GitHub), andpassword_expiry_utcwhen the token has an expiry.GIT_ASKPASSstill handles the interactive login.storeanderasedo nothing.credential.helperthroughGIT_CONFIG_COUNT,GIT_CONFIG_KEY_<n>, andGIT_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(defaulttrue) turns it off.docs/admin/external-auth.md, plus an updated askpass note in the template external auth docs.Testing
coder gitcredential(token-as-username, username and password, no provider, not linked, ignored operations), env appending, and helper quoting.!'<path>/coder' gitcredential):gitcredential getforgithub.comreturned the token andpassword_expiry_utc. Forexample.com, it returned nothing with exit code 0.git ls-remoteon a private repository worked with askpass disabled the way Claude Code disables it (GIT_ASKPASS=andcore.askPass=).claude plugin marketplace add https://github.com/<org>/<private-repo>.git(Claude Code 2.1.302) cloned successfully. Without the helper, it fails withunable to get password from user.git ls-remotetakes 800ms with askpass and 597ms with the helper, or 282ms withgit credential-cachein front of it. The credential lookup alone goes from 508ms (two calls to coderd) to 287ms (one call).GIT_CONFIG_*, helper order, the managed-settings startup install, the provider-not-linked case, and timings.Open questions
!'<path>' gitcredentialform OK on Windows? It uses the same UNIX-style path asGIT_SSH_COMMAND, but I haven't tested it.