af CLI: Don't crash when AF_CONFIG points to a non-regular file#193
Merged
af CLI: Don't crash when AF_CONFIG points to a non-regular file#193
Conversation
Wrappers like `astro otto` set `AF_CONFIG=/dev/null` as a "neutralize
the user's global ~/.af/config.yaml" sentinel. /dev/null is a character
device, so `FileLock(/dev/null.lock)` fails with EPERM on macOS — every
`af` invocation crashes before any command logic runs:
PermissionError: [Errno 1] Operation not permitted: '/dev/null.lock'
ConfigManager now detects when `config_path` is a non-regular file
(directory, fifo, socket, device node) and short-circuits: load()
returns an empty config, save() is a no-op. CLIContext.init() then
resolves URL/auth from env vars (AIRFLOW_API_URL etc.) or falls back
to DEFAULT_AIRFLOW_URL — the same behavior as having no config file.
jlaneve
approved these changes
Apr 27, 2026
Contributor
jlaneve
left a comment
There was a problem hiding this comment.
lgtm. clean fix, well-tested, and composes correctly with #191 to give wrappers a full "no Airflow configured" semantic when both AF_CONFIG=/dev/null and AIRFLOW_API_URL=\"\" are set
filed #194 as a follow-up for the silent-save footgun on af instance add/delete/use and other config-modifying commands. not blocking this PR
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Wrappers like
astro ottosetAF_CONFIG=/dev/nullas a "neutralize the user's global~/.af/config.yaml" sentinel — they don't want the agent to silently inherit a stalecurrent-instancepointer. The intent is reasonable, but/dev/nullis a character device, so whenConfigManager.load()triesFileLock(/dev/null.lock), macOS rejects theO_CREATwith EPERM:This fires for every
afsubcommand —af health,af config version,af dags errors, etc. — and breaks every Otto skill that shells out toaf.This change makes
ConfigManagerdefensive against non-regularAF_CONFIGpaths regardless of who set them.Behavior
In
__init__, compute_null_path = config_path.exists() and not config_path.is_file(). This catches/dev/null, directories, fifos, sockets, and other device nodes — anything where the standard read/lock/write flow can't apply.When
_null_pathis true:load()returns an emptyAirflowCliConfigwithout touching the path or attempting aFileLock.save()is a no-op.CLIContext.init()already prioritizes env vars (AIRFLOW_API_URL,AIRFLOW_USERNAME, etc.) over config values, falling back toDEFAULT_AIRFLOW_URL. With an empty config, the priority chain still resolves correctly — same as if no config file existed.Why this approach
Three options were considered:
load()/save()based on file type (this PR). Single point of detection in__init__, two short-circuits. Future callers automatically get the right behavior.PermissionErrorin_load_from_config. Hides the real exception class, fragile to other EPERM sources, doesn't helpsave()paths.~/.af/config.yamlwhenAF_CONFIGis unusable. Defeats the original "neutralize global config" intent — exactly what the wrapper was trying to avoid.Option 1 keeps the wrapper's semantics ("ignore global config") while removing the crash.
Gotchas
af instance add/delete/useagainst anAF_CONFIG=/dev/nullConfigManager will appear to succeed but persist nothing. This matches the wrapper's intent (don't write to~/.af) and is unreachable in practice — these commands aren't run by the agent. Documenting here for completeness.config_path(af -c /some/dir) now silently behaves as "no config" instead of raising. Same defensible "we got told a non-regular path, treat it as null" semantics. If we ever want to surface a clearer error for accidental misuse, we can add it later — but it's not the bug this PR addresses.