# Reference
## Environment Variables
All FlexLock environment variables are prefixed with `FLEXLOCK_`.
### Parallel Execution
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `FLEXLOCK_POLL_INTERVAL` | int | `10` | Poll interval in seconds for checking task status |
| `FLEXLOCK_LOG_FREQUENCY` | int | `15` | Log frequency in seconds for progress updates |
| `FLEXLOCK_DEFAULT_N_JOBS` | int | `1` | Default number of parallel jobs |
| `FLEXLOCK_DEFAULT_TIMEOUT` | int | `3600` | Default timeout in seconds for HPC jobs |
### Smart Run / Caching
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `FLEXLOCK_WARN_SMART_RUN` | bool | `true` | Warn when `smart_run=True` but `search_dirs=None` |
| `FLEXLOCK_INDEX` | path | *(auto)* | Explicit path to the project-wide fingerprint index DB. Overrides auto-discovery. |
| `FLEXLOCK_INDEX_FALLBACK` | bool | `true` | On an index miss, fall back to the legacy `**/run.lock` glob scan and backfill the index on a hit. |
**Project-wide fingerprint index.** `smart_run` cache hits are served from a
derived SQLite index that maps a run *fingerprint* (config + code tree hashes +
data hashes) to where a completed run lives — a `run.lock` directory **or** a
sweep task `(task_db, task_id)`. This makes sweep items first-class: a config
first run as a sweep task is a cache hit when re-run serially, and vice-versa.
Only successful (`status='done'`) runs are ever served.
The index is a *derived cache* — `run.lock` stays authoritative and the index
can always be deleted and rebuilt with `flexlock reindex
`. Its location is
resolved as: `$FLEXLOCK_INDEX` → the nearest `.flexlock/index.db` walking up
from each `search_dir` (skipping `$HOME`/tmp/filesystem-root) →
`/.flexlock/index.db`. For hits to land, the read and write paths
must resolve to the same file — a project wrapper should set `search_dirs` (and,
if needed, `$FLEXLOCK_INDEX`) so they agree. Auto-indexing is skipped when a run
has no project context (its index would sit directly in `$HOME`/tmp/root).
### Data Hashing
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `FLEXLOCK_CACHE` | path | `~/.cache` | Base dir for the data-hash cache (stored under `/flexlock/hashes_v.db`) |
| `FLEXLOCK_NO_CACHE` | bool | `false` | Force-disable hash caching (overrides a `use_cache=True` argument; never re-enables `use_cache=False`) |
| `FLEXLOCK_DIR_FILE_LIMIT` | int | `1000` | Max files to hash in a directory before warning |
| `FLEXLOCK_CACHE_DIR_FILE_LIMIT` | int | `1000` | Override file limit for cache operations |
### Timestamp Format
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `FLEXLOCK_TIMESTAMP_FORMAT` | str | `%Y-%m-%dT%H-%M-%S` | Format for directory timestamps |
### Logging
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `FLEXLOCK_CONFIGURE_LOGGING` | bool | `true` | Whether to configure logging by default |
### Debug Mode
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `FLEXLOCK_DEBUG` | bool | `false` | Enable debug mode (drops into debugger on error) |
| `FLEXLOCK_NODEBUG` | bool | `false` | Disable debug mode (takes precedence over `FLEXLOCK_DEBUG`) |
| `FLEXLOCK_DEBUG_STRATEGY` | str | `auto` | Debug strategy: `auto`, `pdb`, or `inject` |
**Debug strategies:**
- `auto`: Detect environment - use PDB for scripts, inject locals for notebooks/interactive
- `pdb`: Always use PDB post-mortem debugger
- `inject`: Always inject locals into caller's namespace (for notebooks)
### Boolean Values
Boolean environment variables accept:
- **True**: `1`, `true`, `yes`, `on`
- **False**: `0`, `false`, `no`, `off`
## Exceptions
FlexLock defines a hierarchy of exceptions for error handling.
### Exception Hierarchy
```
FlexLockError (base)
├── FlexLockConfigError
├── FlexLockExecutionError
├── FlexLockSnapshotError
├── FlexLockValidationError
├── FlexLockCacheError
└── FlexLockBackendError
```
### Exception Reference
#### `FlexLockError`
Base exception for all FlexLock errors. Catch this to handle any FlexLock-specific error.
```python
from flexlock import FlexLockError
try:
result = proj.submit(cfg)
except FlexLockError as e:
print(f"FlexLock error: {e}")
```
#### `FlexLockConfigError`
Raised when there is an error in configuration.
**Common causes:**
- Invalid configuration structure
- Missing required keys
- Invalid `_target_` specification
- OmegaConf interpolation errors
```python
from flexlock import FlexLockConfigError
try:
cfg = proj.get('nonexistent_key')
except FlexLockConfigError as e:
print(f"Configuration error: {e}")
```
#### `FlexLockExecutionError`
Raised by FlexLock when execution machinery itself fails (for example,
backend submission errors or task-database corruption).
> **Important:** Exceptions raised **inside the user's target function**
> are *not* wrapped — they propagate as the original exception type
> (e.g. `ValueError`, `RuntimeError`). Use `except Exception` or the
> specific type if you need to catch them. `FlexLockExecutionError`
> only covers FlexLock-side failures around the call.
```python
from flexlock import FlexLockExecutionError
try:
result = proj.submit(cfg)
except FlexLockExecutionError as e:
print(f"FlexLock failed to dispatch: {e}")
except ValueError as e:
print(f"User function raised: {e}")
```
#### `FlexLockSnapshotError`
Raised when there is an error creating or loading snapshots.
**Common causes:**
- Cannot write to `save_dir`
- Corrupted `run.lock` file
- Git repository errors during snapshot
- Invalid data paths for hashing
```python
from flexlock import FlexLockSnapshotError
try:
snapshot(cfg, repos={'main': '.'})
except FlexLockSnapshotError as e:
print(f"Snapshot error: {e}")
```
#### `FlexLockValidationError`
Raised when validation of inputs fails. Currently the main triggers are:
- Per-item sweep `save_dir`s that fall outside the sweep root.
- Sweep source ambiguity (multiple of `sweep` / `sweep_file` /
`sweep_key` given) or a missing `sweep_key`.
- Selection of a non-existent dotted key.
Numeric/range checks like `n_jobs <= 0` are **not** enforced today —
that example would simply behave like `n_jobs=1`.
```python
from flexlock import FlexLockValidationError
try:
proj.submit(cfg, sweep=[{'save_dir': '/elsewhere'}])
except FlexLockValidationError as e:
print(f"Validation error: {e}")
```
#### `FlexLockCacheError`
Raised when there is an error with smart run caching.
**Common causes:**
- Cannot read cached `run.lock`
- Fingerprint comparison errors
- Corrupted cache database
```python
from flexlock import FlexLockCacheError
try:
result = proj.get_result(cfg, search_dirs=['outputs/'])
except FlexLockCacheError as e:
print(f"Cache error: {e}")
```
#### `FlexLockBackendError`
Raised when there is an error with HPC backends (Slurm/PBS).
**Common causes:**
- Invalid backend configuration
- Job submission failure
- Queue/scheduler errors
- Task database lock issues
```python
from flexlock import FlexLockBackendError
try:
result = proj.submit(cfg, pbs_config='pbs.yaml')
except FlexLockBackendError as e:
print(f"HPC backend error: {e}")
```
### Catching Multiple Exceptions
```python
from flexlock import (
FlexLockError,
FlexLockConfigError,
FlexLockExecutionError,
)
try:
cfg = proj.get('train')
result = proj.submit(cfg)
except FlexLockConfigError as e:
print(f"Fix your configuration: {e}")
except FlexLockExecutionError as e:
print(f"Execution failed: {e}")
except FlexLockError as e:
print(f"Other FlexLock error: {e}")
```
## On-Disk Run Files
A run directory is owned by `RunRecord` (`flexlock/run_record.py`). Every file is
written atomically (temp file + `os.replace`).
### `run.lock` (YAML)
The snapshot written *before* execution — the human-editable record. Top-level
keys: `timestamp`, `config` (resolved), and optionally `note`, `repos` (git
tree/commit per tracked repo), `data` (data hashes), `lineage` (upstream runs),
`fingerprint`, `parent`.
```yaml
timestamp: "2026-07-10T10:00:00"
note: "baseline before lr sweep" # only present when --note / note= was passed
config:
_target_: myproject.train.train
save_dir: outputs/exp/train
lr: 0.1
repos:
main: {commit: "abc…", tree: "def…", path: "/repo", is_dirty: false}
data: {train_csv: "xxh64:…"}
lineage:
extract: {path: "/abs/outputs/exp/extract", info: {...}}
fingerprint: "sha256:…"
```
`note` is metadata only — it is excluded from the fingerprint (which hashes
`{config, repos, data}`) and ignored by `RunDiff`, so it never affects caching.
### `run.complete` (JSON)
Success marker written after the user function returns. A cache hit requires
*both* `run.lock` and `run.complete`. Writing it also clears any stale
`run.error`.
### `run.error` (JSON, schema v1)
Failure sidecar written next to `run.lock` when the user function raises. Mirrors
the traceback the task DB also stores, so triage needs no sqlite. Capturing it
never masks the original exception (which still propagates unwrapped).
```json
{"version": 1, "exc_type": "ValueError", "exc_message": "...",
"traceback": "full formatted chain", "timestamp": "ISO-8601",
"task_id": "optional (sweep)", "node": "optional (sweep)"}
```
`KeyboardInterrupt` is deliberately not captured — a bare `run.lock` with no
`run.complete`/`run.error` is the interrupted signature.
### `results.json` (JSON)
The user function's return payload (`{"result": value}` when it isn't a dict).
For the JSON contracts of the query commands (`show`, `graph`, `why`), see
[Agentic Workflows](agentic_workflows.md).