Summary
Surface the expiry of an external auth token (alongside the token itself) so a git credential helper built on coder external-auth access-token can emit git's password_expiry_utc field. This lets git-credential-cache hold a Coder-minted token for exactly its valid lifetime and evict it the moment it rotates.
Background
In a workspace, git HTTPS auth to GitHub is handled by GIT_ASKPASS pointing at the coder binary (coder gitaskpass), which fetches a token from external auth via the control plane. This is always-fresh and correct, but it costs a control-plane round-trip on every credential request (~0.6-1s in our environment), and git requests credentials twice per HTTPS auth (username + password), so each git fetch / push pays roughly 1-2s of auth overhead.
The natural mitigation is to put git-credential-cache in front of the askpass:
[credential "https://github.com"]
helper = cache --timeout=900
This is fast, but git-credential-cache only accepts a fixed --timeout and has no knowledge of the token's real lifetime. Because external auth tokens rotate (GitHub App user-to-server tokens last ~8h), a fixed timeout can cache a token that rotates/expires before the timeout elapses. The cache then serves a stale token until the timeout expires, producing intermittent 401s. The worst-case stale window equals the timeout.
Request
Expose the token's expiry so a credential helper can output password_expiry_utc (git >= 2.34). With it, git and git-credential-cache cache the credential for exactly its valid life and re-invoke the helper the instant it expires: fast and correct, with no fixed-timeout guesswork and no stale window.
A helper would emit:
protocol=https
host=github.com
username=x-access-token
password=<token>
password_expiry_utc=<unix-seconds>
The server already tracks the OAuth expiry (oauth_expiry; see coderd/database/queries/externalauth.sql and coderd/externalauth/externalauth.go); it just isn't surfaced to clients. Today:
coder external-auth access-token <provider> (cli/externalauth.go) prints only the token. --extra reads the provider's OAuth extra map only; for GitHub it returns no extra properties found for token (cli/externalauth.go:91), and the tracked Expiry isn't reachable.
agentsdk.ExternalAuthResponse (codersdk/agentsdk/agentsdk.go:696) carries TokenExtra, URL, Username, Password, but no token expiry.
Any one of these would solve it:
- A
--expiry flag (or a --format json / password_expiry_utc output mode) on coder external-auth access-token.
- Add the expiry to
agentsdk.ExternalAuthResponse so coder gitaskpass and credential helpers can read it.
Option 1 alone is enough to build the helper purely client-side.
Why password_expiry_utc
It is git's intended mechanism for short-lived, rotating credentials: a helper returns the credential plus its expiry, git treats it as invalid after that time and re-invokes the helper. The field was added in git 2.34 (see the git-credential documentation). Without an expiry source, users must choose between a slow per-operation askpass and a fixed-timeout cache that has a stale-token window.
Environment
- Coder v2.33.x (CLI + workspace agent)
- git 2.43.0
- GitHub App-based external auth (token rotates ~8h)
Summary
Surface the expiry of an external auth token (alongside the token itself) so a git credential helper built on
coder external-auth access-tokencan emit git'spassword_expiry_utcfield. This letsgit-credential-cachehold a Coder-minted token for exactly its valid lifetime and evict it the moment it rotates.Background
In a workspace, git HTTPS auth to GitHub is handled by
GIT_ASKPASSpointing at thecoderbinary (coder gitaskpass), which fetches a token from external auth via the control plane. This is always-fresh and correct, but it costs a control-plane round-trip on every credential request (~0.6-1s in our environment), and git requests credentials twice per HTTPS auth (username + password), so eachgit fetch/pushpays roughly 1-2s of auth overhead.The natural mitigation is to put
git-credential-cachein front of the askpass:This is fast, but
git-credential-cacheonly accepts a fixed--timeoutand has no knowledge of the token's real lifetime. Because external auth tokens rotate (GitHub App user-to-server tokens last ~8h), a fixed timeout can cache a token that rotates/expires before the timeout elapses. The cache then serves a stale token until the timeout expires, producing intermittent401s. The worst-case stale window equals the timeout.Request
Expose the token's expiry so a credential helper can output
password_expiry_utc(git >= 2.34). With it, git andgit-credential-cachecache the credential for exactly its valid life and re-invoke the helper the instant it expires: fast and correct, with no fixed-timeout guesswork and no stale window.A helper would emit:
The server already tracks the OAuth expiry (
oauth_expiry; seecoderd/database/queries/externalauth.sqlandcoderd/externalauth/externalauth.go); it just isn't surfaced to clients. Today:coder external-auth access-token <provider>(cli/externalauth.go) prints only the token.--extrareads the provider's OAuthextramap only; for GitHub it returnsno extra properties found for token(cli/externalauth.go:91), and the trackedExpiryisn't reachable.agentsdk.ExternalAuthResponse(codersdk/agentsdk/agentsdk.go:696) carriesTokenExtra,URL,Username,Password, but no token expiry.Any one of these would solve it:
--expiryflag (or a--format json/password_expiry_utcoutput mode) oncoder external-auth access-token.agentsdk.ExternalAuthResponsesocoder gitaskpassand credential helpers can read it.Option 1 alone is enough to build the helper purely client-side.
Why
password_expiry_utcIt is git's intended mechanism for short-lived, rotating credentials: a helper returns the credential plus its expiry, git treats it as invalid after that time and re-invokes the helper. The field was added in git 2.34 (see the git-credential documentation). Without an expiry source, users must choose between a slow per-operation askpass and a fixed-timeout cache that has a stale-token window.
Environment