Skip to content

fix(ruff): fail fix() when ruff exits with an error code instead of silently returning an empty Changeset - #114

Merged
danielgafni merged 1 commit into
typesafe-ai:mainfrom
kittimzhe:fix/ruff-fix-error-exit-code
Oct 11, 2026
Merged

danielgafni merged 1 commit into
typesafe-ai:mainfrom
kittimzhe:fix/ruff-fix-error-exit-code

Conversation

@kittimzhe

Copy link
Copy Markdown
Contributor

What changed

RuffChecker.fix and RuffFormatter.fix ran ruff with expect=dagger.ReturnType.ANY, awaited only combined_output(), and built a Changeset without ever inspecting the exit code. When ruff itself fails — a broken ruff.toml (unknown option), a usage error, a runtime error — the function returned an empty Changeset, which reads as a successful no-op ("everything is already clean"), and ruff's error text never reached the terminal in dagger call. The same input makes lint() fail loudly (it uses the default expect=SUCCESS), so the module disagreed with itself.

Fix

After combined_output(), read exit_code() and raise RuntimeError when it is not 0 or 1:

  • 0 — clean, 1 — violations remain after --fix — are normal outcomes and still return a Changeset exactly as before (an over-strict check would break the "fix what's fixable" flow).
  • 2+ — ruff's error code (configuration / usage / runtime) — now fails the function with the exit code in the message.

lint() is untouched.

Verification (Dagger engine, all six runs against the same source dirs)

# Scenario Before After
1 ruff.toml with an unknown option → check fix empty Changeset (no changes to apply) ✗ FunctionError: ruff check --fix failed with exit code 2
2 same → format fix empty Changeset ✗ same, ruff format
3 fixable violations (F401 unused import) → check fix Changeset with the fix ✓ unchanged
4 fixable + unfixable (F401 + F821, ruff exits 1) → check fix Changeset ✓ still returns the Changeset (no false failure)
5 well-formed source → format fix Changeset ✓ unchanged
6 broken config → lint loud failure (TOML parse error ...) ✓ unchanged

ruff check --config ruff.toml passes on both changed files.

Fixes #99.


Prepared and engine-verified with an AI coding agent; verification matrix above was run with dagger v0.21.10 on darwin/arm64, not inferred from reading the code.

RuffChecker.fix and RuffFormatter.fix used expect=ReturnType.ANY without
inspecting the exit code, so a ruff configuration/usage/runtime error
returned an empty Changeset that reads as a successful no-op, and ruff's
error output was invisible in dagger call. lint() already fails loudly
in the same situations (default expect=SUCCESS), so the two fix paths
disagreed with the rest of the module.

Exit codes 0 (clean) and 1 (violations remain after --fix) still return
a Changeset as before; 2+ now raises RuntimeError with the exit code.

Fixes typesafe-ai#99
@danielgafni
danielgafni merged commit 77e6c9f into typesafe-ai:main Oct 11, 2026
11 of 13 checks passed
@danielgafni

Copy link
Copy Markdown
Contributor

thanks!

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.

Ruff fix functions return successful Changesets after Ruff exits with a usage error

3 participants