Skip to content

Commit ac1633f

Browse files
Copilotpelikhan
andcommitted
Rename OIDC fields for Claude: separate OAuth and API token env vars
- Renamed OIDCConfig.EnvVarName -> OauthTokenEnvVar (for OIDC OAuth token) - Renamed OIDCConfig.FallbackEnvVar -> ApiTokenEnvVar (for API key fallback) - Added GetOAuthTokenEnvVarName() to CodingAgentEngine interface - Claude now uses CLAUDE_CODE_OAUTH_TOKEN for OIDC tokens - Claude uses ANTHROPIC_API_KEY as API key fallback - Updated setup_oidc_token.cjs to distinguish OAuth vs API tokens - Updated schema and tests to reflect new field names - OAuth token from OIDC is exported to CLAUDE_CODE_OAUTH_TOKEN - API key fallback remains as ANTHROPIC_API_KEY Co-authored-by: pelikhan <[email protected]>
1 parent b2aaf10 commit ac1633f

File tree

6 files changed

+96
-65
lines changed

6 files changed

+96
-65
lines changed

pkg/parser/schemas/main_workflow_schema.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2991,13 +2991,13 @@
29912991
"type": "string",
29922992
"description": "URL endpoint to revoke the app token after workflow execution (optional)"
29932993
},
2994-
"env_var_name": {
2994+
"oauth_token_env_var": {
29952995
"type": "string",
2996-
"description": "Environment variable name to store the token. Defaults to engine-specific variable (e.g., ANTHROPIC_API_KEY for Claude)"
2996+
"description": "Environment variable name for OAuth token obtained via OIDC. For Claude: CLAUDE_CODE_OAUTH_TOKEN. Defaults to engine-specific variable."
29972997
},
2998-
"fallback_env_var": {
2998+
"api_token_env_var": {
29992999
"type": "string",
3000-
"description": "Fallback environment variable to use if OIDC token acquisition fails. Typically references a secret (e.g., ${{ secrets.ANTHROPIC_API_KEY }})"
3000+
"description": "Environment variable name for API key used as fallback. For Claude: ANTHROPIC_API_KEY. Defaults to engine-specific variable."
30013001
}
30023002
},
30033003
"required": ["token_exchange_url"],

pkg/workflow/agentic_engine.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,16 @@ type CodingAgentEngine interface {
7575
// Returns nil if the engine does not support OIDC or OIDC is not configured
7676
GetOIDCConfig(workflowData *WorkflowData) *OIDCConfig
7777

78-
// GetTokenEnvVarName returns the environment variable name for authentication tokens
79-
// Used by OIDC token setup to determine where to store the obtained token
78+
// GetTokenEnvVarName returns the environment variable name for API key authentication tokens
79+
// For Claude: ANTHROPIC_API_KEY (used as fallback when OIDC fails)
80+
// For Copilot: GITHUB_TOKEN
81+
// For Codex: OPENAI_API_KEY
8082
GetTokenEnvVarName() string
83+
84+
// GetOAuthTokenEnvVarName returns the environment variable name for OAuth tokens obtained via OIDC
85+
// For Claude: CLAUDE_CODE_OAUTH_TOKEN
86+
// For other engines: typically same as GetTokenEnvVarName()
87+
GetOAuthTokenEnvVarName() string
8188
}
8289

8390
// ErrorPattern represents a regex pattern for extracting error information from logs
@@ -168,12 +175,18 @@ func (e *BaseEngine) GetOIDCConfig(workflowData *WorkflowData) *OIDCConfig {
168175
return nil
169176
}
170177

171-
// GetTokenEnvVarName returns the default token environment variable name
178+
// GetTokenEnvVarName returns the default API token environment variable name
172179
// Engines should override this to return engine-specific values
173180
func (e *BaseEngine) GetTokenEnvVarName() string {
174181
return "GITHUB_TOKEN"
175182
}
176183

184+
// GetOAuthTokenEnvVarName returns the default OAuth token environment variable name
185+
// By default, uses the same as API token. Engines can override for different OAuth token variables.
186+
func (e *BaseEngine) GetOAuthTokenEnvVarName() string {
187+
return e.GetTokenEnvVarName()
188+
}
189+
177190
// GetLogFileForParsing returns the default log file path for parsing
178191
// Engines can override this to use engine-specific log files
179192
func (e *BaseEngine) GetLogFileForParsing() string {

pkg/workflow/claude_engine.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,18 @@ func (e *ClaudeEngine) GetOIDCConfig(workflowData *WorkflowData) *OIDCConfig {
9999
}
100100
}
101101

102-
// GetTokenEnvVarName returns the environment variable name for Claude's authentication token
102+
// GetTokenEnvVarName returns the environment variable name for Claude's API key authentication
103+
// This is used as the fallback when OIDC authentication is not available
103104
func (e *ClaudeEngine) GetTokenEnvVarName() string {
104105
return "ANTHROPIC_API_KEY"
105106
}
106107

108+
// GetOAuthTokenEnvVarName returns the environment variable name for Claude's OAuth token
109+
// This is used for OIDC-obtained tokens
110+
func (e *ClaudeEngine) GetOAuthTokenEnvVarName() string {
111+
return "CLAUDE_CODE_OAUTH_TOKEN"
112+
}
113+
107114
// GetExecutionSteps returns the GitHub Actions steps for executing Claude
108115
func (e *ClaudeEngine) GetExecutionSteps(workflowData *WorkflowData, logFile string) []GitHubActionStep {
109116
// Handle custom steps if they exist in engine config
@@ -218,10 +225,10 @@ func (e *ClaudeEngine) GetExecutionSteps(workflowData *WorkflowData, logFile str
218225
// Add environment section - always include environment section for GH_AW_PROMPT
219226
stepLines = append(stepLines, " env:")
220227

221-
// Add Anthropic API key - if OIDC is configured, use the token from the setup step
222-
// Otherwise, use the secret directly
228+
// Add authentication token - if OIDC is configured, use OAuth token from setup step
229+
// Otherwise, use the API key secret directly
223230
if oidcConfig != nil {
224-
stepLines = append(stepLines, " ANTHROPIC_API_KEY: ${{ steps.setup_oidc_token.outputs.token }}")
231+
stepLines = append(stepLines, " CLAUDE_CODE_OAUTH_TOKEN: ${{ steps.setup_oidc_token.outputs.token }}")
225232
} else {
226233
stepLines = append(stepLines, " ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}")
227234
}

pkg/workflow/js/setup_oidc_token.cjs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -118,43 +118,43 @@ async function main() {
118118
// Get configuration from environment variables
119119
const audience = process.env.GH_AW_OIDC_AUDIENCE;
120120
const exchangeUrl = process.env.GH_AW_OIDC_EXCHANGE_URL;
121-
const envVarName = process.env.GH_AW_OIDC_ENV_VAR_NAME;
122-
const fallbackEnvVar = process.env.GH_AW_OIDC_FALLBACK_ENV_VAR;
121+
const oauthTokenEnvVar = process.env.GH_AW_OIDC_OAUTH_TOKEN_ENV_VAR;
122+
const apiTokenEnvVar = process.env.GH_AW_OIDC_API_TOKEN_ENV_VAR;
123123

124-
if (!audience || !exchangeUrl || !envVarName) {
125-
core.setFailed("Missing required OIDC configuration (audience, exchange_url, or env_var_name)");
124+
if (!audience || !exchangeUrl || !oauthTokenEnvVar || !apiTokenEnvVar) {
125+
core.setFailed("Missing required OIDC configuration (audience, exchange_url, oauth_token_env_var, or api_token_env_var)");
126126
return;
127127
}
128128

129-
// Check if token was provided as fallback
130-
const fallbackToken = fallbackEnvVar ? process.env[fallbackEnvVar] : null;
129+
// Check if API token was provided as fallback
130+
const apiToken = process.env[apiTokenEnvVar];
131131

132-
if (fallbackToken) {
133-
core.info(`Using provided token from ${fallbackEnvVar} for authentication`);
134-
core.setOutput("token", fallbackToken);
135-
core.setOutput("token_source", "fallback");
136-
core.exportVariable(envVarName, fallbackToken);
132+
if (apiToken) {
133+
core.info(`Using provided API token from ${apiTokenEnvVar} for authentication`);
134+
core.setOutput("token", apiToken);
135+
core.setOutput("token_source", "api_token");
136+
core.exportVariable(apiTokenEnvVar, apiToken);
137137
return;
138138
}
139139

140140
// Get OIDC token with retry
141141
const oidcToken = await retryWithBackoff(() => getOidcToken(audience));
142142

143-
// Exchange OIDC token for app token with retry
144-
const appToken = await retryWithBackoff(() => exchangeForAppToken(oidcToken, exchangeUrl));
143+
// Exchange OIDC token for OAuth app token with retry
144+
const oauthToken = await retryWithBackoff(() => exchangeForAppToken(oidcToken, exchangeUrl));
145145

146-
// Set the token in the environment for subsequent steps
147-
core.info(`Setting token in environment variable: ${envVarName}`);
148-
core.setOutput("token", appToken);
149-
core.setOutput("token_source", "oidc");
150-
core.exportVariable(envVarName, appToken);
146+
// Set the OAuth token in the environment for subsequent steps
147+
core.info(`Setting OAuth token in environment variable: ${oauthTokenEnvVar}`);
148+
core.setOutput("token", oauthToken);
149+
core.setOutput("token_source", "oauth");
150+
core.exportVariable(oauthTokenEnvVar, oauthToken);
151151

152152
// Also output the token for post-step revocation
153153
core.setOutput("oidc_token_obtained", "true");
154154
} catch (error) {
155155
// Only set failed if we get here - workflow validation errors will return before this
156156
core.setFailed(
157-
`Failed to setup token: ${error instanceof Error ? error.message : String(error)}\n\nIf you instead wish to use a custom token, provide it via the fallback environment variable.`
157+
`Failed to setup token: ${error instanceof Error ? error.message : String(error)}\n\nIf you instead wish to use an API token, provide it via the ${apiTokenEnvVar} secret.`
158158
);
159159
}
160160
}

pkg/workflow/openid.go

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ type OIDCConfig struct {
1313
// TokenRevokeURL is the URL to revoke the app token (optional)
1414
TokenRevokeURL string `yaml:"token_revoke_url,omitempty"`
1515

16-
// EnvVarName is the environment variable name to store the token (defaults to token-specific env var)
17-
EnvVarName string `yaml:"env_var_name,omitempty"`
16+
// OauthTokenEnvVar is the environment variable name for the OAuth token obtained via OIDC
17+
// For Claude: CLAUDE_CODE_OAUTH_TOKEN
18+
OauthTokenEnvVar string `yaml:"oauth_token_env_var,omitempty"`
1819

19-
// FallbackEnvVar is the fallback environment variable to use if OIDC fails
20-
FallbackEnvVar string `yaml:"fallback_env_var,omitempty"`
20+
// ApiTokenEnvVar is the environment variable name for the API token used as fallback
21+
// For Claude: ANTHROPIC_API_KEY
22+
ApiTokenEnvVar string `yaml:"api_token_env_var,omitempty"`
2123
}
2224

2325
// ParseOIDCConfig parses OIDC configuration from engine object
@@ -55,17 +57,17 @@ func ParseOIDCConfig(engineObj map[string]any) *OIDCConfig {
5557
}
5658
}
5759

58-
// Extract env_var_name field (optional)
59-
if envVarName, hasEnvVarName := oidcObj["env_var_name"]; hasEnvVarName {
60-
if envVarNameStr, ok := envVarName.(string); ok {
61-
oidcConfig.EnvVarName = envVarNameStr
60+
// Extract oauth_token_env_var field (optional)
61+
if oauthTokenEnvVar, hasOauthTokenEnvVar := oidcObj["oauth_token_env_var"]; hasOauthTokenEnvVar {
62+
if oauthTokenEnvVarStr, ok := oauthTokenEnvVar.(string); ok {
63+
oidcConfig.OauthTokenEnvVar = oauthTokenEnvVarStr
6264
}
6365
}
6466

65-
// Extract fallback_env_var field (optional)
66-
if fallbackEnvVar, hasFallbackEnvVar := oidcObj["fallback_env_var"]; hasFallbackEnvVar {
67-
if fallbackEnvVarStr, ok := fallbackEnvVar.(string); ok {
68-
oidcConfig.FallbackEnvVar = fallbackEnvVarStr
67+
// Extract api_token_env_var field (optional)
68+
if apiTokenEnvVar, hasApiTokenEnvVar := oidcObj["api_token_env_var"]; hasApiTokenEnvVar {
69+
if apiTokenEnvVarStr, ok := apiTokenEnvVar.(string); ok {
70+
oidcConfig.ApiTokenEnvVar = apiTokenEnvVarStr
6971
}
7072
}
7173

@@ -99,8 +101,14 @@ func GenerateOIDCSetupStep(oidcConfig *OIDCConfig, engine CodingAgentEngine) Git
99101

100102
stepLines = append(stepLines, " - name: Setup OIDC token")
101103
stepLines = append(stepLines, " id: setup_oidc_token")
102-
// Only run if the fallback token secret exists (check for non-empty secret)
103-
stepLines = append(stepLines, fmt.Sprintf(" if: secrets.%s != ''", engine.GetTokenEnvVarName()))
104+
105+
// Determine the API token env var (fallback) - check if secret exists before running
106+
apiTokenEnvVar := oidcConfig.ApiTokenEnvVar
107+
if apiTokenEnvVar == "" {
108+
apiTokenEnvVar = engine.GetTokenEnvVarName()
109+
}
110+
stepLines = append(stepLines, fmt.Sprintf(" if: secrets.%s != ''", apiTokenEnvVar))
111+
104112
stepLines = append(stepLines, " uses: actions/github-script@v8")
105113
stepLines = append(stepLines, " env:")
106114

@@ -114,14 +122,17 @@ func GenerateOIDCSetupStep(oidcConfig *OIDCConfig, engine CodingAgentEngine) Git
114122
stepLines = append(stepLines, fmt.Sprintf(" GH_AW_OIDC_EXCHANGE_URL: %s", oidcConfig.TokenExchangeURL))
115123
}
116124

117-
// Use engine's token environment variable name
118-
envVarName := engine.GetTokenEnvVarName()
119-
stepLines = append(stepLines, fmt.Sprintf(" GH_AW_OIDC_ENV_VAR_NAME: %s", envVarName))
125+
// OAuth token environment variable (where OIDC token will be stored)
126+
oauthTokenEnvVar := oidcConfig.OauthTokenEnvVar
127+
if oauthTokenEnvVar == "" {
128+
oauthTokenEnvVar = engine.GetOAuthTokenEnvVarName()
129+
}
130+
stepLines = append(stepLines, fmt.Sprintf(" GH_AW_OIDC_OAUTH_TOKEN_ENV_VAR: %s", oauthTokenEnvVar))
120131

121-
// Use the same env var as fallback
122-
stepLines = append(stepLines, fmt.Sprintf(" GH_AW_OIDC_FALLBACK_ENV_VAR: %s", envVarName))
132+
// API token environment variable (fallback)
133+
stepLines = append(stepLines, fmt.Sprintf(" GH_AW_OIDC_API_TOKEN_ENV_VAR: %s", apiTokenEnvVar))
123134
// Add the actual fallback secret if it exists
124-
stepLines = append(stepLines, fmt.Sprintf(" %s: ${{ secrets.%s }}", envVarName, envVarName))
135+
stepLines = append(stepLines, fmt.Sprintf(" %s: ${{ secrets.%s }}", apiTokenEnvVar, apiTokenEnvVar))
125136

126137
stepLines = append(stepLines, " with:")
127138
stepLines = append(stepLines, " script: |")

pkg/workflow/openid_test.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ func TestOIDCConfigExtraction(t *testing.T) {
1515
"engine": map[string]any{
1616
"id": "claude",
1717
"oidc": map[string]any{
18-
"audience": "test-audience",
19-
"token_exchange_url": "https://api.example.com/token-exchange",
20-
"token_revoke_url": "https://api.example.com/token-revoke",
21-
"env_var_name": "TEST_TOKEN",
22-
"fallback_env_var": "TEST_FALLBACK",
18+
"audience": "test-audience",
19+
"token_exchange_url": "https://api.example.com/token-exchange",
20+
"token_revoke_url": "https://api.example.com/token-revoke",
21+
"oauth_token_env_var": "TEST_OAUTH_TOKEN",
22+
"api_token_env_var": "TEST_API_TOKEN",
2323
},
2424
},
2525
}
@@ -50,12 +50,12 @@ func TestOIDCConfigExtraction(t *testing.T) {
5050
t.Errorf("Expected token revoke URL 'https://api.example.com/token-revoke', got '%s'", config.OIDC.TokenRevokeURL)
5151
}
5252

53-
if config.OIDC.EnvVarName != "TEST_TOKEN" {
54-
t.Errorf("Expected env var name 'TEST_TOKEN', got '%s'", config.OIDC.EnvVarName)
53+
if config.OIDC.OauthTokenEnvVar != "TEST_OAUTH_TOKEN" {
54+
t.Errorf("Expected OAuth token env var 'TEST_OAUTH_TOKEN', got '%s'", config.OIDC.OauthTokenEnvVar)
5555
}
5656

57-
if config.OIDC.FallbackEnvVar != "TEST_FALLBACK" {
58-
t.Errorf("Expected fallback env var 'TEST_FALLBACK', got '%s'", config.OIDC.FallbackEnvVar)
57+
if config.OIDC.ApiTokenEnvVar != "TEST_API_TOKEN" {
58+
t.Errorf("Expected API token env var 'TEST_API_TOKEN', got '%s'", config.OIDC.ApiTokenEnvVar)
5959
}
6060
}
6161

@@ -131,9 +131,9 @@ func TestClaudeEngineWithOIDC(t *testing.T) {
131131
t.Error("Expected OIDC revoke step to be present")
132132
}
133133

134-
// Verify token is used from OIDC setup step
135-
if !strings.Contains(stepsStr, "ANTHROPIC_API_KEY: ${{ steps.setup_oidc_token.outputs.token }}") {
136-
t.Error("Expected ANTHROPIC_API_KEY to use token from OIDC setup step")
134+
// Verify OAuth token is used from OIDC setup step
135+
if !strings.Contains(stepsStr, "CLAUDE_CODE_OAUTH_TOKEN: ${{ steps.setup_oidc_token.outputs.token }}") {
136+
t.Error("Expected CLAUDE_CODE_OAUTH_TOKEN to use token from OIDC setup step")
137137
}
138138

139139
// Verify setup step uses github-script
@@ -176,9 +176,9 @@ func TestClaudeEngineWithoutOIDC(t *testing.T) {
176176
t.Error("Expected OIDC revoke step to be present - Claude has OIDC enabled by default")
177177
}
178178

179-
// Verify token uses OIDC token from setup step
180-
if !strings.Contains(stepsStr, "ANTHROPIC_API_KEY: ${{ steps.setup_oidc_token.outputs.token }}") {
181-
t.Error("Expected ANTHROPIC_API_KEY to use OIDC token from setup step - Claude has OIDC enabled by default")
179+
// Verify OAuth token uses OIDC token from setup step
180+
if !strings.Contains(stepsStr, "CLAUDE_CODE_OAUTH_TOKEN: ${{ steps.setup_oidc_token.outputs.token }}") {
181+
t.Error("Expected CLAUDE_CODE_OAUTH_TOKEN to use OAuth token from setup step - Claude has OIDC enabled by default")
182182
}
183183

184184
// Verify default OIDC configuration values

0 commit comments

Comments
 (0)