Skip to content

Refreshable tokens (6/7): agent-task and CAPI per-request refresh - #14455

Open
babakks wants to merge 1 commit into
babakks/refresh-token-c3-auth-statusfrom
babakks/refresh-token-c4-agent-task-capi
Open

babakks wants to merge 1 commit into
babakks/refresh-token-c3-auth-statusfrom
babakks/refresh-token-c4-agent-task-capi

Conversation

@babakks

@babakks babakks commented Sep 15, 2026 •

Copy link
Copy Markdown
Member

Part of #14449. Based on #14454 (auth status).

Description

The last consumer seam: refresh a short-lived token per agent-task/CAPI request, so agent-task calls made through the CAPI client also travel with a currently valid credential instead of an expired one.

How did you test this change?

This feature is verified end to end as a whole rather than per PR. End-to-end tests should pass.

Key points

  • This closes the last path where a short-lived token could have been used past expiry. The HTTP transport, gh auth token, gh auth status, and the git credential helper already refresh before use; the agent-task/CAPI client is the remaining token consumer, and this PR gives it the same treatment.
  • The refresh is invoked at the point where the CAPI client obtains the token, so an expired access token is rotated before the request goes out rather than causing a failed call.
  • Like the other seams, the refresh goes through the shared serialized path from PR 2, so a CAPI-triggered refresh cannot double-spend the refresh token against a concurrent gh process.

Notes for reviewers

Small PR; focus on where the CAPI client obtains the token and how the refresh is invoked before the request. The behavior mirrors the other consumer seams, so it should read as the same pattern applied once more.

Commit:

  • fix(agent-task/capi): refresh short-lived tokens per CAPI request

Authorship and follow-up

Who wrote this:

  • A human wrote it.
  • An agent wrote it under close human direction.
  • An agent wrote it independently, and no human has guided the implementation beyond the initial prompt.

Who answers review comments:

  • @babakks will read and reply directly.
  • An agent will draft replies and @username will read them before they are posted.
  • Nobody has explicitly committed to replying.

The CAPI transport sets the Authorization header itself, which bypasses the base
HTTP client's auto-refresh. Pass the AuthConfig through to the transport so it
resolves the token through ActiveTokenWithRefresh on each request, renewing a
near-expiry short-lived token just before use.

Co-authored-by: Copilot <[email protected]>
Copilot-Session: 2dc3f23c-61a6-43a7-85cd-90aa872a7fc6

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The new per-request refresh behavior lacks focused unit coverage proving token lookup and header replacement.

Review effort: Balanced
Findings: 1 Low severity

Open (1)
What changed in this PR

Updates agent-task CAPI requests to resolve and refresh credentials immediately before each request.

Changes:

  • Injects authentication configuration into the CAPI client.
  • Resolves the active token per request.
  • Adapts existing tests to the new constructor signature.
File Description
pkg/​cmd/​agent-task/​shared/​capi.go Passes authentication configuration to CAPI.
pkg/​cmd/​agent-task/​capi/​client.go Adds per-request token refresh.
pkg/​cmd/​agent-task/​capi/​job_test.go Adds the authentication test stub.
pkg/​cmd/​agent-task/​capi/​sessions_test.go Updates client construction in session tests.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +78 to +79
cred, _, _ := ct.authCfg.ActiveTokenWithRefresh(ct.host)
req.Header.Set("Authorization", "Bearer "+cred.Token)

@williammartin williammartin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't leave a review comment on here but this doesn't fix insecure-storage because that creates a new entry in hosts.yml and

tokenSourceIsDeviceFlow := source == "oauth_token" || source == "keyring"
// Tokens with "gho_" prefix are OAuth tokens.
//
// TODO: this matches a token prefix itself. It could ask
// gh.AuthConfig.ActiveTokenType instead.
tokenIsOAuth := strings.HasPrefix(token, "gho_")
// Reject if the token is not from a device flow source or is not an OAuth token
if !tokenSourceIsDeviceFlow || !tokenIsOAuth {

Doesn't check this. Also, this whole idea that only oauth tokens are stored in hosts.yml is just broken i.e. --with-token.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants