How the .env Configuration File Is Loaded and Parsed in Claude-Video

The .env configuration file in Claude-Video is loaded from ~/.config/watch/.env and parsed by a custom read_env_file() function in skills/watch/scripts/config.py that handles quoted values, inline comments, and fallbacks to environment variables.

The bradautomates/claude-video repository implements a custom configuration system for its /watch skill, storing user settings and API keys in plain-text .env files rather than relying on external dependencies like python-dotenv. Understanding how this system discovers, parses, and merges configuration values is essential for customizing the tool's behavior across different environments.

Default File Location and Path Resolution

The primary configuration file is defined as a constant in skills/watch/scripts/config.py:

CONFIG_DIR  = Path.home() / ".config" / "watch"
CONFIG_FILE = CONFIG_DIR / ".env"

By default, the system looks for settings at ~/.config/watch/.env. This path is used as the canonical location for user-wide configuration, created automatically during initial setup. However, the codebase also supports alternative locations in specific contexts, such as project-level .env files in the current working directory or skill-specific paths.

Parsing Logic with read_env_file

The core parsing logic resides in the read_env_file() function, which returns a dictionary of key-value pairs without modifying the process environment:

def read_env_file(path: Path | None = None) -> dict[str, str]:
    if path is None:
        path = CONFIG_FILE
    values: dict[str, str] = {}
    if not path.exists():
        return values
    try:
        lines = path.read_text(encoding="utf-8").splitlines()
    except OSError:
        return values
    for line in lines:
        raw = line.strip()
        if not raw or raw.startswith("#") or "=" not in raw:
            continue
        key, _, value = raw.partition("=")
        value = value.strip()
        if len(value) >= 2 and value[0] in ('"', "'") and value[-1] == value[0]:
            value = value[1:-1]
        else:
            for i, ch in enumerate(value):
                if ch == "#" and i > 0 and value[i - 1] in " \t":
                    value = value[:i].rstrip()
                    break
        values[key.strip()] = value
    return values

This implementation performs four critical parsing steps:

  1. Validation: Skips empty lines, comment lines starting with #, and malformed lines lacking an = delimiter.
  2. Key-value separation: Uses partition("=") to split only on the first equals sign, preserving values that contain = characters.
  3. Quote handling: Automatically strips surrounding single or double quotes from values.
  4. Inline comment removal: For unquoted values, removes trailing comments (defined as # preceded by whitespace) while preserving # characters inside quoted strings.

Handling Missing or Unreadable Files

If the specified path does not exist or encounters an OSError during reading (such as permission errors), read_env_file() returns an empty dictionary rather than raising an exception. This design ensures the skill remains functional in restricted environments, falling back to hardcoded defaults or environment variables.

Configuration Resolution Hierarchy

The get_config() function in the same module establishes a three-tier precedence system for the WATCH_DETAIL setting:

def get_config() -> dict[str, object]:
    file_values = read_env_file()
    detail = (
        os.environ.get("WATCH_DETAIL")
        or file_values.get("WATCH_DETAIL")
        or DEFAULT_DETAIL
    )
    if detail not in DETAILS:
        detail = DEFAULT_DETAIL
    return {
        "detail": detail,
        "config_file": str(CONFIG_FILE),
    }

Resolution order:

  • Process environment (os.environ) takes highest priority
  • .env file values serve as secondary fallback
  • Built-in defaults (e.g., "balanced") provide final fallback values

This hierarchy allows users to override configuration temporarily via shell exports without modifying persistent files.

Multi-Location Lookup for API Keys

The Whisper transcription helper (skills/watch/scripts/whisper.py) extends this pattern for sensitive API credentials like GROQ_API_KEY and OPENAI_API_KEY. It searches three candidate locations sequentially:

dotenv_paths = [
    Path.home() / ".config" / "watch" / ".env",
    Path.cwd() / ".env",
    Path(__file__).parent.parent / ".env",
]

For each candidate path, the helper invokes the same read_env_file() parsing logic, enabling both user-wide and project-specific configuration strategies.

Practical Usage Examples

Loading the active configuration programmatically:

from skills.watch.scripts import config

settings = config.get_config()
print(settings["detail"])        # Current detail level

print(settings["config_file"])   # Path to active .env file

Parsing a custom environment file:

from pathlib import Path
from skills.watch.scripts.config import read_env_file

custom_env = Path("/tmp/staging.env")
variables = read_env_file(custom_env)
api_key = variables.get("GROQ_API_KEY")

Summary

  • Primary location: ~/.config/watch/.env defined in skills/watch/scripts/config.py
  • Parser function: read_env_file() handles quotes, inline comments, and malformed lines
  • Precedence: Environment variables override .env values, which override defaults
  • Fallback behavior: Missing or unreadable files return empty dictionaries, preventing runtime crashes
  • Extended usage: whisper.py searches multiple paths (user, project, and skill directories) for API credentials

Frequently Asked Questions

Where does Claude-Video look for the .env file?

By default, the system checks ~/.config/watch/.env as defined by CONFIG_FILE in skills/watch/scripts/config.py. The Whisper helper additionally searches the current working directory and the skill's parent directory for project-specific overrides.

How does the parser handle comments in .env files?

The read_env_file() function skips lines starting with # entirely. For unquoted values, it removes inline comments where # appears after whitespace. Quoted values preserve all content including # characters to prevent truncation of API keys or passwords containing hash symbols.

Can environment variables override .env settings?

Yes. The get_config() function explicitly checks os.environ before consulting the file dictionary, allowing temporary overrides via shell exports like WATCH_DETAIL=high without modifying the configuration file.

What happens if the .env file is missing?

If the file does not exist or cannot be read due to permissions, read_env_file() returns an empty dictionary. Downstream functions then rely on environment variables or built-in defaults, ensuring the application continues to function with baseline settings.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →