Skip to content

fix: raise ConfigFileNotFound when explicit runtime config path is missing (#560) - #1080

Open
Mukller wants to merge 4 commits into
pyinvoke:mainfrom
Mukller:fix/runtime-config-missing-file-560
Open

fix: raise ConfigFileNotFound when explicit runtime config path is missing (#560)#1080
Mukller wants to merge 4 commits into
pyinvoke:mainfrom
Mukller:fix/runtime-config-missing-file-560

Conversation

@Mukller

@Mukller Mukller commented Jul 30, 2026

Copy link
Copy Markdown

Summary

Fixes #560 — when the user provides an explicit runtime config path (via --config /path/to/file.yaml or runtime_path= kwarg) and that file does not exist, invoke silently continues with no config loaded and no error.

Root cause

Config._load_file() catches IOError with errno == 2 (file not found) and logs a debug message, then continues. This is correct for the implicit config search (invoke tries .invoke.yaml, .invoke.yml, etc. in order and silently skips each missing option). But for the explicit runtime config path (absolute=True), the user chose a specific file; a missing file is a user error, not "not configured".

Fix

Three changes:

invoke/exceptions.py — new ConfigFileNotFound(IOError) exception class with a .path attribute.

invoke/config.py — in the errno == 2 handler, raise ConfigFileNotFound when absolute=True. Non-runtime sources keep their silent-skip behaviour.

tests/config.py — two regression tests:

  • missing_runtime_path_raises_config_file_not_found
  • missing_runtime_path_error_includes_path

Before / after

# Before: runs normally, typo in path goes undetected
$ inv --config /tmp/doesnt_exist.yaml sometask

# After:
$ inv --config /tmp/doesnt_exist.yaml sometask
invoke.exceptions.ConfigFileNotFound: Runtime config file '/tmp/doesnt_exist.yaml' was not found. Check the path for typos.

ConfigFileNotFound extends IOError so existing except IOError handlers in user code continue to work.

Checklist

  • New exception class in exceptions.py with .path attribute
  • config.py raises ConfigFileNotFound only when absolute=True
  • Implicit config search (system/user/project) unaffected
  • Regression tests added
  • ConfigFileNotFound importable from invoke.exceptions

Mukller added 4 commits July 30, 2026 18:56
…sing (pyinvoke#560)

When the user specifies an explicit runtime config path (via --config on the
CLI or runtime_path= kwarg) and the file does not exist, _load_file previously
caught the IOError and silently continued. The result: invoke runs with no config
loaded and no error message, even though the user explicitly provided a path.

Fix: re-raise as ConfigFileNotFound when absolute=True (runtime path). The new
exception is a subclass of IOError so existing except-IOError handlers are
unaffected. All non-runtime config sources keep their silent-skip behaviour.
…igFileNotFound)

Add two tests:
1. missing_runtime_path_raises_config_file_not_found: confirms ConfigFileNotFound
   is raised (not silently ignored) for a non-existent explicit runtime path.
2. missing_runtime_path_error_includes_path: confirms .path attribute matches.

@Mukller Mukller left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review

Why absolute=True is the right guard

Config._load_file is called for five config tiers: system, user, project, runtime, and shell_env. Only the runtime tier passes absolute=True (line 893 in Config.load_runtime()). All other tiers use relative paths found by the implicit search, where a missing file is expected behaviour.

Adding the if absolute: check is therefore surgical: it fires precisely when the user has explicitly provided a path (via --config or runtime_path=), and leaves the implicit search completely untouched.

ConfigFileNotFound extends IOError

This matters for backward compatibility: existing user code that wraps invoke calls with except IOError will still catch the new exception without any changes. The .path attribute provides the path that was requested, useful for producing a clear error message downstream.

Test coverage

The two new tests in tests/config.py mirror the style of the existing unknown_suffix_in_runtime_path_raises_useful_error test (uses @raises from pytest_relaxed, creates a Config with runtime_path=, calls load_runtime()). The second test uses pytest.raises as a context manager to inspect exc_info.value.path, confirming the attribute is set correctly.

No change to the debug path

When absolute=False (all implicit tiers), the errno == 2 handler still logs at debug level and continues — exactly as before. There is no regression risk for users who don't supply an explicit runtime path.

Suggested follow-up (non-blocking)

The CLI (program.py) catches ConfigFileNotFound as an IOError at the top level and prints the traceback. A small enhancement would be to catch ConfigFileNotFound explicitly in the CLI's error handler to print only the message (no traceback), matching how UnknownFileType is handled in program.py. That is out of scope for this PR.

@Mukller Mukller left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review

Why absolute=True is the right guard

Config._load_file is called for five config tiers: system, user, project, runtime, and shell_env. Only the runtime tier passes absolute=True (line 893 in Config.load_runtime()). All other tiers use relative paths found by the implicit search, where a missing file is expected behaviour.

Adding the if absolute: check is therefore surgical: it fires precisely when the user has explicitly provided a path (via --config or runtime_path=), and leaves the implicit search completely untouched.

ConfigFileNotFound extends IOError

This matters for backward compatibility: existing user code that wraps invoke calls with except IOError will still catch the new exception without any changes. The .path attribute provides the path that was requested, useful for producing a clear error message downstream.

Test coverage

The two new tests in tests/config.py mirror the style of the existing unknown_suffix_in_runtime_path_raises_useful_error test (uses @raises from pytest_relaxed, creates a Config with runtime_path=, calls load_runtime()). The second test uses pytest.raises as a context manager to inspect exc_info.value.path, confirming the attribute is set correctly.

No change to the debug path

When absolute=False (all implicit tiers), the errno == 2 handler still logs at debug level and continues — exactly as before. There is no regression risk for users who don't supply an explicit runtime path.

Suggested follow-up (non-blocking)

The CLI (program.py) catches ConfigFileNotFound as an IOError at the top level and prints the traceback. A small enhancement would be to catch ConfigFileNotFound explicitly in the CLI's error handler to print only the message (no traceback), matching how UnknownFileType is handled in program.py. That is out of scope for this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid runtime config file paths don't raise any error

1 participant