Auto-fix Rules¶
Whittl ships with 75+ regex-based auto-fix rules that catch common mistakes AI models make when generating Python code. They run in two places:
- During generation — after the AI produces code but before it hits your editor, the fixers scan for known mistakes and correct them silently.
- After a crash — if the generated app fails to run, the AI iterates on fixes through a tool-use loop with hard safeguards.
Both paths compound. The static rules catch 80% of common mistakes instantly (cost: zero extra tokens). The iterating auto-fix handles the novel or complex failures.
When auto-fix runs¶
Post-generation (always on)¶
Every time the AI generates or modifies code, auto-fix runs as the final step before the code is written to disk. This catches:
- Trailing whitespace and missing end-of-file newlines
- Imports that hallucinated (QApplication from PySide6.QtWidgets vs QtCore)
- Deprecated APIs (e.g.,
ft.icons→ft.Iconsin Flet) - Qt enum typos (
Qt.AlignCenter→Qt.AlignmentFlag.AlignCenter) - SQLite parameter tuple mistakes (missing trailing comma)
- Duplicate
### FILE:markers in multi-file output
Zero token cost. Zero user intervention. Just silent correction.
Post-crash iteration (opt-in AI-powered)¶
If the generated app fails to run, Whittl offers to iterate via AI auto-fix:
- Run the app.
- App crashes with a traceback.
- Whittl reads the traceback, identifies the failure line.
- AI tries a tool-based edit (
edit_code) targeting just the broken line. - Re-run. If it works, done. If not, try again.
- Up to 5 rounds total (hard cap, see below).
This path DOES cost tokens, but each round is much cheaper than a full regeneration because it's a surgical edit:
- Round on Claude Haiku: ~$0.005
- Round on Sonnet: ~$0.02–$0.05
- Round on Qwen3-Coder free: $0
Safeguards¶
Auto-fix has four safeguards that protect you from cost-blowout scenarios:
1. Hard round cap (5)¶
No auto-fix cycle ever runs more than 5 rounds. Period. A competent fix lands in 1–3 rounds; more than 5 means the model is flailing. The cap fires a clear message:
Auto-fix stopped after 5 rounds without resolving the error. A competent fix usually lands in 1–3 rounds; more than this means the model is flailing. Try editing the code directly, rephrasing the request, or switching to a stronger model (Claude Opus/Sonnet, GPT-5, or Gemini 2.5 Pro).
2. Oscillation guard¶
If the same error fingerprint (error type + file + line) trips 3+ times across a sliding 6-entry window, the cycle aborts. This catches A→B→A→B→A loops where the AI alternates between two wrong fixes.
3. Read-only bailout¶
If the AI spends 3+ consecutive rounds only reading code without editing, the cycle aborts. Prevents infinite "thinking" loops where the model repeatedly examines code without committing to a fix.
4. Stop button¶
The Stop button in the chat panel persists across auto-fix rounds. Click once to cancel the queued round. Works even mid-API-call.
Rule categories¶
Auto-fix rules cluster into nine categories. Each category has a "what it catches" and "when it fires" shape:
Python syntax hygiene (13 rules)¶
- Trailing whitespace removal
- Missing end-of-file newline
- Trailing comma in import statements
- Tab-vs-space mixing
- Missing
# -*- coding: utf-8 -*-when non-ASCII chars present - Missing
__future__imports where needed for older Python - F-string f-prefix recovery (when AI writes
"Hello {name}"without f-prefix) - Duplicate import statement dedup
- Unreachable code elimination after return
PySide6 / PyQt patterns (12 rules)¶
pyqtSignal→Signal(PySide6 convention)pyqtSlot→SlotQActionmoved from QtWidgets to QtGui (Qt6 change)- Qt enum upgrades (
Qt.AlignCenter→Qt.AlignmentFlag.AlignCenter,Qt.Horizontal→Qt.Orientation.Horizontal, etc.) QVariantconversion methods stripped (not needed in Python)QApplication.quit()outside signal handler- Missing
app.exec()at module tail - Reserve missing imports (QLineEdit, QScrollArea, QScrollBar, and the ~30 most-commonly-missed Qt widgets)
Flet patterns (13 rules)¶
ft.icons→ft.Icons(mobile API change)ft.colors→ft.Colorspage.dialogdeprecation →page.open()/page.close()padding=removed from Column/Row (Flet requires Container for padding)- Tab content= parameter removal (Tabs are label-only in current Flet)
- String
"center"vsft.MainAxisAlignment.CENTER(string alignments auto-upgrade) - Hallucinated control names (ft.FloatingButton → ft.FloatingActionButton, etc.)
- Missing
ft.app(target=...)at module tail - Deprecated
ft.window_width/ft.window_height→page.window.width/page.window.height
SQLite patterns (4 rules)¶
- Single-parameter tuple missing trailing comma (
execute("SELECT ...", (id))→execute("SELECT ...", (id,))) - Cross-variation detection for multiline execute calls
- Triple-quoted SQL + separate params variable
- Named parameter dict style validation
Import fixing (8 rules)¶
- Missing imports detected via AST (vs. regex-based in previous versions; catches nested and lazy imports)
- Common Qt widget imports auto-added when used but not imported
- Local module detection (don't pip-install a folder that exists locally)
- Python stdlib vs pip package disambiguation
- Package name vs module name mismatch (PIL → pillow, cv2 → opencv-python, yaml → pyyaml)
CustomTkinter patterns (5 rules)¶
ctk.mainloop()missing at tailtk.BooleanVar→ctk.BooleanVar(CTk wraps these)- Window appearance mode not set before geometry
- Missing
customtkinterimport whenctk.is used - Icon assignment before
deiconify()
Threading safety (6 rules)¶
- Widget access from worker threads (flags for signal-based dispatch)
- Callback without
QTimer.singleShotdispatch from non-Qt thread threading.Threadwithoutdaemon=Trueflag- Missing
join()on critical threads before shutdown - Audio callback inside a
QMutex(deadlock pattern) QThreadvsthreading.Threaduse-case picker
File path handling (8 rules)¶
os.path.joinvs string concatenation- Path traversal protection on AI-generated code (refuses
..paths) - Case-sensitive filename handling for Linux compat
- Temp file cleanup in
finallyblock - File handle leak pattern (missing
withstatement)
Miscellaneous (6+ rules)¶
- JSON serialization of non-serializable objects
- Unclosed string literals at EOL
- Comment typo corrections ("pyqt5" → "PySide6", "Pthon" → "Python")
- Common typo fixes in keywords (
funciton→function,retrun→return) - Hallucinated API call normalizations
Viewing the full rule list¶
The canonical list lives in core/autofix_rules.py in the Whittl installation. For a human-readable view:
- Open Whittl's terminal panel
- Run
python -c "from core.autofix_rules import _ALL_RULES; print('\n'.join(r['name'] for r in _ALL_RULES))"(future v2.4 feature)
Or simply watch the [AUTO-FIX] log lines as you generate code — they print which specific rules fired.
How new rules get added¶
Whittl's _auto_learned.md file captures patterns from every successful auto-fix. When you or the dev (that's Lynden) notices a pattern that fires repeatedly, it graduates from "auto-learned via skill injection" to a proper regex rule in core/autofix_rules.py.
This is how the library grew from 30+ rules in v2.1 to 75+ in v2.3. Every release pulls new rules out of the auto-learned file and promotes them to hardcoded fast-path fixes.
Disabling auto-fix¶
If you're debugging a generation issue and want to see the raw AI output without auto-fix intervention:
Edit → Preferences → AI Generation → Enable AI Auto-Fix (uncheck)
This disables post-crash AI iteration. The post-generation static rules always run — they're non-destructive and too valuable to turn off.
Troubleshooting¶
Auto-fix is stuck on the same error
The oscillation guard should catch this within 3 tries. If it somehow doesn't:
- Click Stop in the chat panel.
- Read the error yourself and manually edit the code.
- Report the specific error pattern so a rule can be added to prevent future cases.
Auto-fix made my code wrong
Rare but possible on edge cases. Use:
- Edit → Undo (Ctrl+Z) to revert the last auto-fix
- History panel to roll back to a previous generation
- Uncheck Enable AI Auto-Fix temporarily
Then re-generate or edit manually.
Auto-fix is making generation slow
Post-generation auto-fix adds 50–200ms per generation. Post-crash iteration is what adds real time (minutes, not seconds). If you're seeing long waits:
- Watch the
[AUTO-FIX]log lines to see if post-crash iteration is running - Check the round counter in the status bar
- Click Stop if the cycle is clearly not converging
What's next¶
- Skills System — the markdown-based complement to hardcoded rules
- Agent Mode — how auto-fix interacts with extended tool loops
- Debugging a Crash — when auto-fix doesn't solve it, what to do manually