AccessToken returns a currently valid access token, refreshing it if needed, or "" if the session is not authorized (or a refresh has failed and re-authorization is required). It is cheap to call repeatedly: the underlying token source caches and only refreshes when the token has expired.
()
| 93 | // re-authorization is required). It is cheap to call repeatedly: the underlying |
| 94 | // token source caches and only refreshes when the token has expired. |
| 95 | func (m *Manager) AccessToken() string { |
| 96 | m.mu.Lock() |
| 97 | src := m.source |
| 98 | m.mu.Unlock() |
| 99 | if src == nil { |
| 100 | return "" |
| 101 | } |
| 102 | // Refresh (if needed) happens here, off the lock, because ReuseTokenSource may |
| 103 | // make a blocking network call and holding m.mu would serialize every tool call. |
| 104 | tok, err := src.Token() |
| 105 | if err != nil { |
| 106 | // A refresh failure (expired GitHub App refresh token, revoked grant, or a |
| 107 | // network blip) leaves the session unauthorized and forces a re-login. |
| 108 | // Surface it once, otherwise it only manifests as a surprise re-authorization |
| 109 | // prompt. The oauth2 error carries the token endpoint's response, not the |
| 110 | // access or refresh token. |
| 111 | m.mu.Lock() |
| 112 | if !m.refreshErrLogged { |
| 113 | m.refreshErrLogged = true |
| 114 | m.logger.Warn("OAuth token refresh failed; re-authorization required", "error", err) |
| 115 | } |
| 116 | m.mu.Unlock() |
| 117 | return "" |
| 118 | } |
| 119 | if !tok.Valid() { |
| 120 | return "" |
| 121 | } |
| 122 | return tok.AccessToken |
| 123 | } |
| 124 | |
| 125 | // HasToken reports whether a valid token is currently available. |
| 126 | func (m *Manager) HasToken() bool { |
no outgoing calls