# Postmortem: Ruff Format Collapsing Multi-Line Chained Method Calls Against Developer Intent
## Summary
• Ruff formatter unexpectedly collapsed multiline chained method calls into a single line
• Attempts to use `preview = true` and adjust `pyproject.toml` configuration didn't preserve desired formatting
• Behavior occurred inconsistently based on trailing comments or argument length
## Root Cause
• Ruff's formatter uses heuristic line-breaking rules lacking explicit "force multiline" markers
• Configuration gaps exist between:
- Existing GitHub issue [#8049](https://github.com/astral-sh/ruff/issues/8049) tracking multiline chaining
- Actual implementation status (Preview feature partially shipped/unstable)
• Ambiguous interaction between:
- Parentheses wrapping
- Embedded comments
- Method call length
## Why This Happens in Real Systems
• Auto-formatters prioritize consistency over contextual readability needs
• Complex chained calls fall into Ruff's "expression breaking" gray area where:
- Rule-based heuristics misjudge developer intent
- Lack of explicit syntax (like trailing `\`) forces guessing
• Legacy code migration pain points:
- Existing stylistic patterns conflict with formatter defaults
- Gradual adoption of Preview features causes inconsistency
## Real-World Impact
• **Readability degradation**: Critical visual grouping signals lost in long chains
```python
# Collapsed version obscures workflow sequence
(greeter.greet("Anton").greet("Anabelle").greet("Aaron"))
• Version control churn: Battles between formatter/developer causing repeated revert-format cycles
• Onboarding friction: New engineers struggle with unexpected formatting changes
• Tool distrust: Engineers bypass formatting for critical sections via # noqa
Example or Code
Original with working parentheses + comment:
(
greeter # Explicit comment preserves multiline
.greet("Anton")
.greet("Anabelle")
.greet("Aaron")
)
Failing case with argument-length sensitivity:
# Formatter collapses first two chains
(
main_menu
.click_on_main_menu("short").click_on_main_menu_item("this_string_is_too_long")
)
How Senior Engineers Fix It
- Immediate workaround: Inject comments to force breaks
(main_menu .method1() # force-break .method2() ) - Strategic defense: Wrap chain segments in parentheses
(method.one()).two().three() - Toolchain adaptation: Combine Ruff with
blackvia--formatflag for compound chains - Config freeze: Disable unstable preview features until behavior stabilizes
[tool.ruff] preview = false # Revert to stable formatting rules - Team alignment: Standardize trailing
# fmt: skipannotations for critical chains
Why Juniors Miss It
• Assume formatters handle all readability concerns “magically”
• Lack context about Ruff’s Preview feature lifecycle stage
• Unaware of heuristic limitations:
- Comment placement sensitivity
- Parentheses interaction pitfalls
• Hesitate to disable formatting after investing in linting pipeline setup
• Focus on functionality over formatting side-effects# Junior developer's expectation vs reality expectation: (chain .line() .breaks() )
reality:
(chain.line().breaks()) # Why??