Summary
Two related UX gaps in --eval-filter. Both classify as "filter expression is malformed" and should behave the same way: emit a clear single-line warning and return no rows.
- Type-mismatch is silent. The filter namespace receives
exit_code as int, so exit_code == '0' compares int to str and never matches. The column displays "0" and -f json emits "0" as a string, so the failing pattern looks right. The result is zero rows, no warning, no hint.
- NameError is misattributed. An undefined identifier in the expression raises
NameError during eval, but the current handler blames the file: Failed to load file <_io.TextIOWrapper name='.duct/logs/..._info.json' mode='r' encoding='utf-8'>: name 'foo' is not defined. The JSON load succeeded; the filter eval is at fault. The message also leaks the Python _io.TextIOWrapper repr and repeats once per file.
Reproducer — type mismatch (silent)
cd "$(mktemp -d)"
duct -- echo one >/dev/null
duct --fail-time 0 -- bash -c 'exit 1'
con-duct ls # 2 rows, EXIT_CODE shows "0" / "1"
con-duct ls --eval-filter "exit_code == '0'" # 0 rows (silent)
con-duct ls --eval-filter "exit_code == 0" # 1 row
Reproducer — NameError misattribution
cd "$(mktemp -d)"
duct -- true
con-duct ls --eval-filter "foo == 'bar'"
# [WARNING] con_duct.ls: Failed to load file <_io.TextIOWrapper ...>: name 'foo' is not defined
Proposed behavior
One warning per invocation (not per file), blaming the expression:
- Type-mismatch:
[WARNING] --eval-filter: 'exit_code' is int, compared to str — expression will never match
- NameError:
[WARNING] --eval-filter: name 'foo' is not defined
Implementation-shape: split the try/except in ls.py that today wraps both the JSON load and the filter eval. JSON errors stay file-scoped; eval errors are expression-scoped and emit once.
Low-risk prerequisite: the filter expression can be pre-analyzed once per invocation (not once per file) to detect literal-type mismatches against the known field types (exit_code: int, message: str, etc.).
Scope
Reproduces on v0.19.0 and current main. Longstanding, not a regression. Surfaced during pre-0.20.0 QE.
Summary
Two related UX gaps in
--eval-filter. Both classify as "filter expression is malformed" and should behave the same way: emit a clear single-line warning and return no rows.exit_codeasint, soexit_code == '0'compares int to str and never matches. The column displays"0"and-f jsonemits"0"as a string, so the failing pattern looks right. The result is zero rows, no warning, no hint.NameErrorduring eval, but the current handler blames the file:Failed to load file <_io.TextIOWrapper name='.duct/logs/..._info.json' mode='r' encoding='utf-8'>: name 'foo' is not defined. The JSON load succeeded; the filter eval is at fault. The message also leaks the Python_io.TextIOWrapperrepr and repeats once per file.Reproducer — type mismatch (silent)
Reproducer — NameError misattribution
Proposed behavior
One warning per invocation (not per file), blaming the expression:
Implementation-shape: split the try/except in
ls.pythat today wraps both the JSON load and the filter eval. JSON errors stay file-scoped; eval errors are expression-scoped and emit once.Low-risk prerequisite: the filter expression can be pre-analyzed once per invocation (not once per file) to detect literal-type mismatches against the known field types (
exit_code: int,message: str, etc.).Scope
Reproduces on v0.19.0 and current
main. Longstanding, not a regression. Surfaced during pre-0.20.0 QE.