Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions pkg/cli/add_interactive_secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,17 @@ func TestAddInteractiveConfig_resolveEngineApiKeyCredential(t *testing.T) {
}
}

func TestAddInteractiveConfig_resolveEngineApiKeyCredential_ClaudeOAuthToken(t *testing.T) {
t.Setenv("ANTHROPIC_API_KEY", "")
t.Setenv("CLAUDE_CODE_OAUTH_TOKEN", "private-oauth-token")

config := &AddInteractiveConfig{EngineOverride: "claude"}
_, _, err := config.resolveEngineApiKeyCredential()
require.ErrorContains(t, err, "CLAUDE_CODE_OAUTH_TOKEN")
require.ErrorContains(t, err, "ANTHROPIC_API_KEY")
assert.NotContains(t, err.Error(), "private-oauth-token")
}

func TestAddInteractiveConfig_configureEngineAPISecret_noWriteAccess(t *testing.T) {
t.Parallel()
tests := []struct {
Expand Down
9 changes: 9 additions & 0 deletions pkg/cli/engine_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (

var engineSecretsLog = logger.New("cli:engine_secrets")

var errUnsupportedClaudeOAuthToken = errors.New("CLAUDE_CODE_OAUTH_TOKEN is not supported for Claude workflows; use ANTHROPIC_API_KEY or configure Anthropic Workload Identity Federation instead")

// Overridable for testing
var (
engineSecretsPromptFn = func(req SecretRequirement, config EngineSecretConfig) error {
Expand Down Expand Up @@ -337,6 +339,9 @@ func ensureSecretAvailable(req SecretRequirement, config EngineSecretConfig) err
}

// Secret not found, prompt user for it
if req.IsEngineSecret && req.EngineName == string(constants.ClaudeEngine) && os.Getenv("CLAUDE_CODE_OAUTH_TOKEN") != "" { //nolint:osgetenvlibrary
return errUnsupportedClaudeOAuthToken
}
return engineSecretsPromptFn(req, config)
}

Expand Down Expand Up @@ -706,6 +711,10 @@ func GetEngineSecretNameAndValue(engine string, existingSecrets map[string]struc
}
}

if engine == string(constants.ClaudeEngine) && value == "" && os.Getenv("CLAUDE_CODE_OAUTH_TOKEN") != "" { //nolint:osgetenvlibrary
return "", "", false, errUnsupportedClaudeOAuthToken
Comment on lines +714 to +715

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 324e108: TestGetEngineSecretNameAndValue now clears CLAUDE_CODE_OAUTH_TOKEN at setup; the focused OAuth test still sets it explicitly.

}

return secretName, value, false, nil
}

Expand Down
60 changes: 60 additions & 0 deletions pkg/cli/engine_secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,35 @@ func TestEnsureSecretAvailable_ExistingCopilotSecret(t *testing.T) {
})
}

func TestEnsureSecretAvailableClaudeOAuthToken(t *testing.T) {
t.Setenv("ANTHROPIC_API_KEY", "")
t.Setenv("CLAUDE_CODE_OAUTH_TOKEN", "private-oauth-token")
req := SecretRequirement{
Name: "ANTHROPIC_API_KEY",
IsEngineSecret: true,
EngineName: string(constants.ClaudeEngine),
}
originalPrompt := engineSecretsPromptFn
t.Cleanup(func() { engineSecretsPromptFn = originalPrompt })
prompted := false
engineSecretsPromptFn = func(_ SecretRequirement, _ EngineSecretConfig) error {
prompted = true
return nil
}

err := ensureSecretAvailable(req, EngineSecretConfig{})
require.ErrorIs(t, err, errUnsupportedClaudeOAuthToken)
assert.NotContains(t, err.Error(), "private-oauth-token")
assert.False(t, prompted)

t.Run("existing repository secret is accepted", func(t *testing.T) {
err := ensureSecretAvailable(req, EngineSecretConfig{
ExistingSecrets: map[string]struct{}{"ANTHROPIC_API_KEY": {}},
})
require.NoError(t, err)
})
}

func TestStringContainsSecretName(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -621,6 +650,37 @@ func TestGetEngineSecretNameAndValue(t *testing.T) {
})
}

func TestGetEngineSecretNameAndValueClaudeOAuthToken(t *testing.T) {
t.Setenv("ANTHROPIC_API_KEY", "")
t.Setenv("CLAUDE_CODE_OAUTH_TOKEN", "private-oauth-token")

t.Run("unsupported token alone", func(t *testing.T) {
_, _, _, err := GetEngineSecretNameAndValue("claude", nil)
require.Error(t, err)
require.ErrorContains(t, err, "CLAUDE_CODE_OAUTH_TOKEN")
require.ErrorContains(t, err, "ANTHROPIC_API_KEY")
require.ErrorContains(t, err, "Workload Identity Federation")
assert.NotContains(t, err.Error(), "private-oauth-token")
})

t.Run("API key in environment takes precedence", func(t *testing.T) {
t.Setenv("ANTHROPIC_API_KEY", "supported-api-key")
name, value, exists, err := GetEngineSecretNameAndValue("claude", nil)
require.NoError(t, err)
assert.Equal(t, "ANTHROPIC_API_KEY", name)
assert.Equal(t, "supported-api-key", value)
assert.False(t, exists)
})

t.Run("repository secret takes precedence", func(t *testing.T) {
name, value, exists, err := GetEngineSecretNameAndValue("claude", map[string]struct{}{"ANTHROPIC_API_KEY": {}})
require.NoError(t, err)
assert.Equal(t, "ANTHROPIC_API_KEY", name)
assert.Empty(t, value)
assert.True(t, exists)
})
}

func TestMustValidateExistingSecretValue(t *testing.T) {
t.Run("copilot engine secret requires revalidation", func(t *testing.T) {
req := SecretRequirement{
Expand Down
Loading