Repository navigation
Conversation
Co-Authored-By: Claude Code 2.1.285 / Claude Opus 5.5 <noreply@anthropic.com>
The summary template was only rendered after the command finished, so a typo cost the whole run and its traceback replaced the command's exit code. Render it against typed placeholder values at parse time instead; argparse reports the error and exits 2. Since argparse also applies type= to string defaults, a bad DUCT_SUMMARY_FORMAT (env or .env) is caught the same way. Co-Authored-By: Claude Code 2.1.285 / Claude Opus 5.5 <noreply@anthropic.com>
SummaryFormatter.format_field caught ValueError and printed the raw value
with a warning, silently discarding the user's spec (e.g. {peak_rss:.2d}
printed 123456). With the template now validated before the command
runs, a bad spec fails there for free, so fail instead of guessing
(con#192).
Validation renders with colors both off and on, since the custom
conversions return colored strings: {wall_clock_time!X:.3f} (Python's
conversion-first order) works without colors but not with them. The
error suggests duct's {field:spec!conv} order when it spots the other.
Co-Authored-By: Claude Code 2.1.285 / Claude Opus 5.5 <noreply@anthropic.com>
execute() renders --summary-format after the command finished; an exception there replaced the command's exit code with 1. Log an error (pointing at info.json when it was written) and return the command's exit code. The CLI now validates the template up front, but execute() can be called directly, and after the run a duct failure must not cost the user the run. Co-Authored-By: Claude Code 2.1.285 / Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #454 +/- ##
==========================================
+ Coverage 92.03% 92.63% +0.59%
==========================================
Files 15 15
Lines 1344 1358 +14
Branches 182 184 +2
==========================================
+ Hits 1237 1258 +21
+ Misses 71 69 -2
+ Partials 36 31 -5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
It is a user-facing behavior change to CLI argument handling and exit-code semantics (previously-tolerated formats and env defaults now hard-fail at parse time), which warrants a human maintainer's compatibility sign-off despite being well-tested.
Review effort: Balanced
Findings: None
What changed in this PR
This PR makes con-duct run reject a malformed --summary-format at argparse parse time (exit 2, one-line error, no traceback) instead of crashing after the command runs and clobbering its exit code. It removes the silent str fallback in SummaryFormatter.format_field that used to mask bad specs, and adds a defensive guard around the post-run render so a render failure never costs the user a completed (potentially expensive) run. This implements the "fail fast, but never punish a finished run" philosophy now codified in CLAUDE.md, closing #192.
Changes:
- Add
validate_summary_format+VALIDATE_SUMMARY_PLACEHOLDERSand wire them into a new argparsetype=_summary_format_type, which also validates theDUCT_SUMMARY_FORMAT/.envdefault and emits a conversion-order hint for{field!conv:spec}. - Drop the
ValueError→strfallback informat_field; guard the post-run render inexecute()to log an error and preserve the command's exit code and logs. - Add tests for parse-time rejection, env-var rejection, the conversion-order hint, placeholder/type parity with a real run, and exit-code preservation on post-run render failure.
| File | Description |
|---|---|
src/con_duct/_formatter.py |
Removes silent str fallback; adds VALIDATE_SUMMARY_PLACEHOLDERS and validate_summary_format to render a template against typed placeholders. |
src/con_duct/cli.py |
Adds _summary_format_type argparse type (with conversion-order hint) and applies it to --summary-format. |
src/con_duct/_duct_main.py |
Wraps the post-run summary render in try/except, logging an error while preserving the command's exit code and logs. |
test/test_formatter.py |
Adds spec-mismatch, default-validation, and placeholder-parity tests; updates the conversion-order expectation to ValueError. |
test/test_cli.py |
Adds parse-time rejection, env-var rejection, pass-through, and conversion-order-hint tests. |
test/duct_main/test_execution.py |
Adds a test that a post-run render failure keeps the command's exit code and logs. |
CLAUDE.md |
Documents the fail-fast-before-run vs. loud-fallback-after-run convention. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
A malformed
--summary-formatis now rejected before the command runs (exit 2, one-line error, no traceback) instead of crashing after the run and replacing the command's exit code. Thestrfallback that silently discarded a bad format spec is removed.Changes
--summary-formatgets an argparsetype=that renders the template against typed placeholder values (VALIDATE_SUMMARY_PLACEHOLDERS), once with colors off and once on. argparse appliestype=to string defaults too, so a badDUCT_SUMMARY_FORMAT(env or.env) is caught the same way; the error names all three sources.SummaryFormatter.format_field:{peak_rss:.2d}used to print the raw123456plus a warning; it now fails validation.execute(): if rendering still fails (e.g.execute()called directly), log an error and return the command's exit code.{field!conv:spec}order: duct's conversions return colored strings, so{wall_clock_time!X:.3f}fails with colors; the error suggests{wall_clock_time:.3f!X}.Example:
Behavior changes to note
{wall_clock_time:d},{start_time:%Y},{average_rss:,d}), and{field!X:spec}when it only works without colors.test_execution_summary_formatted_wall_clock_time_nowvalidpinned the fallback for{wall_clock_time!X:.3f}with colors; it now expects theValueError.Verification
tox -e py312: 497 passed;tox -e lint,typingclean.Report'sexecution_summaryafter two samples (mutation-checked: an intaverage_rssplaceholder fails it);execute()with a bad template returns the command's exit code and keeps the logs.Why placeholders, and why the type test
A spec error depends only on the value's type (
Nonerenders as-before the spec is applied), so rendering against one correctly typed value per key reproduces exactly what the post-run render would do, no stricter and no looser. Python accepts float specs on ints ({peak_rss:.2f}) but notdon floats, and the validator inherits that exactly. The type test keeps the placeholders honest as summary fields change.except Exceptionin the argparse type: formats can also raiseAttributeError({peak_rss.foo}) orTypeError({peak_rss[0]}); catching onlyKeyError/IndexError/ValueErrorleft those as a traceback or argparse's generic "invalid value" message.🤖 Generated with Claude Code