[BUG] CLAUDE_CODE_API_KEY_HELPER_TTL_MS cache not working - apiKeyHelper called on every API request
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
The CLAUDE_CODE_API_KEY_HELPER_TTL_MS environment variable is configured and being passed to the apiKeyHelper script correctly, but the cache/TTL mechanism is not working. The apiKeyHelper script is being called on nearly every API request (approximately every 5 minutes) instead of respecting the configured 1-hour (3600000ms) TTL. This causes frequent re-authentication prompts for users with interactive authentication methods like 1Password CLI, AWS SSO, or custom OAuth flows.
What Should Happen?
The apiKeyHelper script should be called only once per configured TTL period (e.g., once every hour for CLAUDE_CODE_API_KEY_HELPER_TTL_MS: "3600000"). The API key should be cached internally by Claude Code and reused for subsequent requests until the TTL expires.
Error Messages/Logs
Audit log showing apiKeyHelper being called every ~5 minutes in the same session:
14:52:10 | PID: 9228 | CacheTTL: 3600000
14:53:37 | PID: 51848 | CacheTTL: 3600000 (1min 27s later)
14:58:37 | PID: 16508 | CacheTTL: 3600000 (5min later)
15:04:02 | PID: 44396 | CacheTTL: 3600000 (5min 25s later)
15:09:55 | PID: 19648 | CacheTTL: 3600000 (5min 53s later)
Note: Different PIDs indicate script is being invoked repeatedly, not reusing cached credentials.
Steps to Reproduce
- Create ~/.claude/settings.json:
{
"apiKeyHelper": "pwsh -NoProfile -Command \"& 'c:/Users/username/.claude/get-api-key.ps1'\"",
"env": {
"CLAUDE_CODE_API_KEY_HELPER_TTL_MS": "3600000"
}
}
- Create get-api-key.ps1 with logging:
#!/usr/bin/env pwsh
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Add-Content -Path "$HOME/.claude/api-key-access.log" -Value "$timestamp | PID: $PID | CacheTTL: $env:CLAUDE_CODE_API_KEY_HELPER_TTL_MS"
op read --no-newline "op://vault/item/credential"
- Use Claude Code CLI in a single session/conversation without closing terminal
- Monitor ~/.claude/api-key-access.log - observe script being called every ~5 minutes
- Verify with /status command that apiKeyHelper is configured correctly
Expected: Script called once per hour
Actual: Script called every ~5 minutes
Claude Model
Sonnet (default)
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.0.37
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
Windows Terminal
Additional Information
/statusoutput confirms: Auth token:apiKeyHelperand API key:apiKeyHelper✅CLAUDE_CODE_API_KEY_HELPER_TTL_MSis correctly passed to script (visible in logs) ✅- Same session/conversation with no restarts ✅
- Affects both CLI and VS Code Extension
Impact: Users with interactive authentication (1Password CLI requiring approval every ~5min, AWS SSO, OAuth flows, hardware keys) must re-authenticate multiple times per session, severely degrading usability. Workaround implemented: Manual caching within the apiKeyHelper script using Windows DPAPI encryption successfully caches for configured duration, confirming the issue is with Claude Code's built-in cache, not the authentication method:
$cacheFile = "$HOME/.claude/.api-key-cache"
$cacheTTLSeconds = 3600
if (Test-Path $cacheFile) {
$cacheData = Get-Content $cacheFile -Raw | ConvertFrom-Json
$cacheAge = (Get-Date) - [DateTime]$cacheData.timestamp
if ($cacheAge.TotalSeconds -lt $cacheTTLSeconds) {
# Decrypt with Windows DPAPI and return cached key
$encryptedBytes = [Convert]::FromBase64String($cacheData.encryptedKey)
$decryptedBytes = [System.Security.Cryptography.ProtectedData]::Unprotect($encryptedBytes, $null, 'CurrentUser')
[System.Text.Encoding]::UTF8.GetString($decryptedBytes)
exit
}
}
# Fetch and cache new key with DPAPI encryption
$apiKey = op read --no-newline "op://vault/item/credential"
$encryptedBytes = [System.Security.Cryptography.ProtectedData]::Protect([System.Text.Encoding]::UTF8.GetBytes($apiKey), $null, 'CurrentUser')
@{
timestamp = (Get-Date).ToString("o")
encryptedKey = [Convert]::ToBase64String($encryptedBytes)
} | ConvertTo-Json | Set-Content $cacheFile -NoNewline
$apiKey
This workaround proves the cache mechanism works when implemented at script level, suggesting the bug is in Claude Code's TTL implementation.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗