How Whisper API Keys Are Loaded From Environment Variables vs Config Files in claude-video
The claude-video repository loads Whisper API keys by first checking environment variables (GROQ_API_KEY or OPENAI_API_KEY), then falling back to a user-specific ~/.config/watch/.env config file if the environment variables are unset.
The watch skill implements a two-stage loading mechanism that prioritizes environment variables for CI/CD and containerized deployments while supporting a local config file for interactive development. This design keeps secrets out of version control without sacrificing developer convenience.
Backend Selection Drives Key Loading
The loading process begins with backend selection. In skills/watch/scripts/watch.py (lines 239-257), the load_api_key() function determines which provider to use based on the --whisper CLI flag:
- Groq — cheaper inference, preferred default
- OpenAI — fallback when Groq is unavailable
If no flag is provided, the code attempts Groq first and automatically falls back to OpenAI when a Groq key cannot be found.
Two-Tier Key Resolution: Environment Variables First
The load_api_key() function in skills/watch/scripts/whisper.py implements ordered precedence:
| Backend | Environment Variable | Config File Key |
|---|---|---|
| Groq | GROQ_API_KEY |
groq_api_key |
| OpenAI | OPENAI_API_KEY |
openai_api_key |
Step 1: Environment Variable Check
# From skills/watch/scripts/whisper.py
import os
def load_api_key(preferred_backend=None):
# Determine which backend to use
backend = preferred_backend or "groq"
# Map backend to environment variable name
env_var_map = {
"groq": "GROQ_API_KEY",
"openai": "OPENAI_API_KEY"
}
# First: check environment variable
api_key = os.getenv(env_var_map[backend])
if api_key:
return backend, api_key
# Fall through to config file...
Step 2: Config File Fallback
When the environment variable is absent, the function loads ~/.config/watch/.env using python-dotenv:
# From skills/watch/scripts/whisper.py (continued)
from pathlib import Path
from dotenv import dotenv_values
def _load_user_config():
"""Load user-specific configuration from ~/.config/watch/.env"""
config_dir = Path.home() / ".config" / "watch"
config_dir.mkdir(parents=True, exist_ok=True)
env_path = config_dir / ".env"
return dotenv_values(env_path) if env_path.exists() else {}
def load_api_key(preferred_backend=None):
# ... environment variable check above ...
# Second: check config file
config = _load_user_config()
config_key_map = {
"groq": "groq_api_key",
"openai": "openai_api_key"
}
api_key = config.get(config_key_map[backend])
if api_key:
return backend, api_key
# No key found — return None to trigger error handling
return None, None
The config directory path is centralized in skills/watch/scripts/config.py to ensure consistency across the codebase.
Error Handling When Both Sources Fail
The caller in skills/watch/scripts/watch.py handles missing keys gracefully:
# From skills/watch/scripts/watch.py
from whisper import load_api_key, transcribe_video
backend, api_key = load_api_key(args.whisper)
if not api_key:
print(
f"[watch] whisper fallback failed: missing API key for {backend}",
file=sys.stderr,
)
sys.exit(1)
# Proceed with transcription
transcript = transcribe_video(video_path, backend, api_key)
Practical Configuration Examples
Environment Variable Setup (Production/CI)
# Export for current session
export GROQ_API_KEY="gsk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# Or use a .env file loaded by your orchestrator
docker run -e GROQ_API_KEY="$GROQ_API_KEY" claude-video watch "https://youtu.be/..."
Config File Setup (Local Development)
# Create directory with restricted permissions
mkdir -p ~/.config/watch
chmod 700 ~/.config/watch
# ~/.config/watch/.env — keep this file private (chmod 600)
groq_api_key=gsk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
openai_api_key=sk-openai-yyyyyyyyyyyyyyyyyyyyyyyyyyyy
chmod 600 ~/.config/watch/.env
Using the CLI With Explicit Backend
# Use default (Groq with fallback to OpenAI)
watch "https://youtu.be/dQw4w9WgXcQ"
# Force OpenAI Whisper
watch "https://youtu.be/dQw4w9WgXcQ" --whisper openai
# Force Groq (no fallback)
watch "https://youtu.be/dQw4w9WgXcQ" --whisper groq
Key Source Files
| File | Purpose |
|---|---|
skills/watch/scripts/whisper.py |
Implements load_api_key() and backend-specific transcription logic |
skills/watch/scripts/watch.py |
CLI entry point that calls load_api_key() and handles errors |
skills/watch/scripts/config.py |
Centralizes paths including ~/.config/watch directory |
tests/test_whisper.py |
Validates key loading, chunking, and error scenarios |
Summary
- Environment variables take precedence — set
GROQ_API_KEYorOPENAI_API_KEYfor deployment scenarios - Config file provides convenience — store keys in
~/.config/watch/.envfor local development - Backend selection is flexible — use
--whisperflag or accept automatic Groq→OpenAI fallback - Graceful degradation — missing keys produce clear error messages rather than cryptic failures
- Security-conscious defaults — config directory creation with proper permissions, no keys in repository
Frequently Asked Questions
What happens if both GROQ_API_KEY and OPENAI_API_KEY are set?
The code uses backend selection logic first, not key availability. If you don't specify --whisper, it attempts Groq (GROQ_API_KEY). If you pass --whisper openai, it uses OPENAI_API_KEY regardless of whether Groq credentials exist.
Can I use a custom config file location?
Not directly through the current implementation. The path ~/.config/watch/.env is hardcoded in skills/watch/scripts/config.py. To override, set the appropriate environment variable instead, which always takes precedence.
Why does the code prefer Groq over OpenAI?
According to source comments in watch.py, Groq offers significantly cheaper inference for Whisper transcription. The automatic fallback to OpenAI ensures functionality when Groq rate limits or availability issues occur.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →