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 <dir>. Its location is resolved as: $FLEXLOCK_INDEX → the nearest .flexlock/index.db walking up from each search_dir (skipping $HOME/tmp/filesystem-root) → <results_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_CACHE>/flexlock/hashes_v<N>.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.

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

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.

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

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_dirs 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.

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

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

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

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.

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).

{"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.