Previously, \`ExternalAuthResponse\` contained no expiry information, so
workspace agents and git credential helpers had no way to know when a
cached token would stop being valid. Every git operation had to call
back to coderd via \`GIT_ASKPASS\` to get a fresh token, adding 1-2
seconds of latency.
This PR surfaces \`OAuthExpiry\` from the database as \`ExpiresAt\` in
\`ExternalAuthResponse\`, allowing agents to cache tokens with correct
eviction timing (compatible with \`git-credential-cache --timeout\` and
\`password_expiry_utc\` introduced in git 2.34).
\`ExpiresAt\` is normalized to UTC before JSON encoding to avoid
sub-minute precision loss that occurs when the PostgreSQL driver applies
historical Local Mean Time (LMT) timezone offsets to year-1 AD
timestamps.
The \`coder external-auth access-token\` CLI command gains \`--output
json\` to print the full response including \`ExpiresAt\`, enabling
scripts to consume the expiry without parsing heuristics.
Closes https://github.com/coder/coder/issues/26036
## Manual Test
<details>
<summary>Setup</summary>
1. Create a GitHub OAuth app at https://github.com/settings/developers
with:
- Homepage URL: `http://127.0.0.1:3000`
- Authorization callback URL:
`http://127.0.0.1:3000/external-auth/github/callback`
2. Start the dev server with the GitHub provider configured:
```sh
CODER_EXTERNAL_AUTH_0_ID=github CODER_EXTERNAL_AUTH_0_TYPE=github
CODER_EXTERNAL_AUTH_0_CLIENT_ID=<client-id>
CODER_EXTERNAL_AUTH_0_CLIENT_SECRET=<client-secret> ./scripts/develop.sh
```
3. Log in at `http://127.0.0.1:3000` (use `127.0.0.1`, not `localhost`,
so the OAuth state cookie domain matches the callback URL).
4. Go to Account > External Authentication and click **Connect** next to
GitHub. Complete the OAuth flow.
5. Create a workspace and SSH into it:
```sh
coder create test-workspace
coder ssh test-workspace
```
</details>
<details>
<summary>Flow 1: Token is valid — JSON output includes
<code>expires_at</code></summary>
Inside the workspace, run:
```sh
coder external-auth access-token github --output json
echo "Exit code: $?"
```
Expected output (GitHub tokens have no expiry, so \`expires_at\` is the
zero value):
```json
{
"access_token": "<redacted>",
"token_extra": null,
"url": "",
"type": "github",
"expires_at": "0001-01-01T00:00:00Z",
"username": "<redacted>",
"password": ""
}
```
```
Exit code: 0
```
</details>
<details>
<summary>Flow 2: Token missing — JSON output includes auth URL, exit
code 1</summary>
Disconnect GitHub in the Coder UI (Account > External Authentication >
Disconnect), then inside the workspace run:
```sh
coder external-auth access-token github --output json
echo "Exit code: $?"
```
Expected output:
```json
{
"access_token": "",
"token_extra": null,
"url": "http://127.0.0.1:3000/external-auth/github",
"type": "",
"expires_at": "0001-01-01T00:00:00Z",
"username": "",
"password": ""
}
```
```
Exit code: 1
```
</details>
> This PR was authored by Mux on behalf of Mike.
## Summary
Adds support for multiple peer root workspace agents sharing the same
`auth_instance_id`, so AWS, Azure, and GCP instance-identity auth can
issue the correct session token for a selected agent instead of assuming
a
single root agent per instance.
## Problem
When a Terraform template attaches two or more `coder_agent` resources
(with `auth = "aws-instance-identity"`) to a single compute instance,
every agent shares the same cloud instance ID. The existing singular
lookup picks whichever agent was created most recently, silently
ignoring
the others.
## Solution
Introduce an optional pre-auth agent selector (`CODER_AGENT_NAME`) and
make the server-side lookup ambiguity-aware.
**Database layer:**
- `GetWorkspaceAgentsByInstanceID` (`:many`): returns all matching root
agents for an instance ID.
- `GetWorkspaceAgentByInstanceIDAndName` (`:one`): returns the named
root
agent for disambiguation.
**SDK and CLI:**
- `agent_name` field added to AWS, Azure, and GCP request structs
(`omitempty` for backward compatibility).
- `CODER_AGENT_NAME` env var and `--agent-name` flag wired into the
agent
bootstrap before instance-identity auth runs.
**Server handler (`handleAuthInstanceID`):**
- When `agent_name` is present: direct lookup by (instance ID, name).
- When absent: legacy lookup, then resource-scoped ambiguity check.
Returns 409 with available agent names if multiple root agents match.
- Whitespace-only names are trimmed and treated as unspecified.
- Sub-agents remain excluded (`parent_id IS NULL` filter).
**Verification template:**
- `examples/templates/aws-multi-agent/` provisions one EC2 instance with
two agents (`main` and `dev`), both using instance-identity auth with
`CODER_AGENT_NAME` set in the cloud-init user data.
## Backward compatibility
Existing single-agent deployments work unchanged. The `agent_name` field
is optional with `omitempty`, and the unnamed path preserves today's
behavior when only one root agent matches.
Refactors Agent instance identity to be a SessionTokenProvider.
Refactors the CLI to create Agent clients via a centralized function, rather than add-hoc via individual command handlers and their flags.
This allows commands besides `coder agent`, but which still use the agent identity, to support instance identity authentication.
Fixes#19111 by unifying all API requests to go thru the SessionTokenProvider for auth credentials.