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

> Discover how Claude-Video loads and parses .env configuration files. Learn about custom functions, quoted values, inline comments, and environment variable fallbacks.

- Repository: [bradautomates/claude-video](https://github.com/bradautomates/claude-video)
- Tags: how-to-guide
- Published: 2026-08-07

---

**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`](https://github.com/bradautomates/claude-video/blob/main/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`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/config.py):

```python
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:

```python
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:

```python
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`](https://github.com/bradautomates/claude-video/blob/main/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:

```python
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:

```python
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:

```python
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`](https://github.com/bradautomates/claude-video/blob/main/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`](https://github.com/bradautomates/claude-video/blob/main/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`](https://github.com/bradautomates/claude-video/blob/main/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.