Skip to content

fix(interface): exit cleanly on unhandled LLM BadRequestError (#510)#590

Open
Rome-1 wants to merge 1 commit into
usestrix:mainfrom
Rome-1:fix/issue-510-badrequest-clean-exit
Open

fix(interface): exit cleanly on unhandled LLM BadRequestError (#510)#590
Rome-1 wants to merge 1 commit into
usestrix:mainfrom
Rome-1:fix/issue-510-badrequest-clean-exit

Conversation

@Rome-1

@Rome-1 Rome-1 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Fixes #510

When an unhandled provider error (e.g. openai.BadRequestError / HTTP 400) reaches the top-level main() handler, the bare raise re-surfaces a raw Python traceback — and in the PyInstaller binary release, a "Failed to execute script" crash dump — even though the CLI/TUI layer has already rendered a user-friendly error panel. This replaces the raise with sys.exit(1), so the process still exits non-zero but without the confusing traceback. The finally block (report cleanup with status failed, posthog/scarf end) still runs, and the success-path display code after the try/finally correctly stays unreached.

Testing

  • pytest tests/test_main_exit.py — 1 passed. The test mocks arg parsing, Docker, env validation, telemetry, and asyncio.run, makes the scan run raise openai.BadRequestError, and asserts main() raises SystemExit with code 1 rather than propagating the provider error. Confirmed it fails on current main (the BadRequestError propagates) and passes with this change.
  • ruff check and ruff format --check clean on the diff; pyupgrade (UP) rules clean via ruff; bandit -c pyproject.toml clean.
  • Note: the repo's pinned pre-commit mypy hook errors out internally on the bundled openai SDK on main as well, so mypy is not asserted here.

…ix#510)

When run_cli/run_tui raise an unhandled exception (e.g. openai.BadRequestError
from a provider 400), main() re-raised it, surfacing a raw traceback and a
PyInstaller crash dump in the binary release after the CLI/TUI had already
rendered a user-friendly error panel. Exit with status 1 instead so the
finally-block cleanup still runs while suppressing the noisy traceback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a UX regression where unhandled LLM provider errors (e.g., openai.BadRequestError / HTTP 400) caused a raw Python traceback — or a PyInstaller crash dump — to appear in the terminal even though the CLI/TUI had already shown a user-friendly error panel. The fix replaces raise with sys.exit(1) in the top-level except Exception handler, and adds a test covering the exact failure path.

  • main.py: sys.exit(1) in the except Exception block still lets the finally cleanup (report_state.cleanup, telemetry end) run correctly because SystemExit propagates through finally.
  • tests/test_main_exit.py: New test properly accounts for the LLM warm-up asyncio.run call at line 756 and verifies SystemExit(1) is raised rather than BadRequestError propagating.

Confidence Score: 4/5

Safe to merge for the intended use case; the finally cleanup path is unaffected and the test validates the core fix.

The fix correctly resolves the traceback/crash-dump problem and the finally block runs as expected. The one open question is that the broad except Exception handler will now silently swallow unexpected code bugs (not just provider errors) without printing anything to the terminal, which could complicate incident triage. That tradeoff may be intentional for a packaged CLI, but it's worth a conscious decision.

The except Exception block in strix/interface/main.py around line 824 deserves a second look to decide whether the silent-exit behaviour should be scoped to known provider error types only.

Important Files Changed

Filename Overview
strix/interface/main.py Replaces bare raise with sys.exit(1) in the broad except Exception handler in main(); the finally block still runs correctly, but the catch is broader than just provider errors.
tests/test_main_exit.py New test that mocks all dependencies and verifies main() raises SystemExit(1) on an openai.BadRequestError; correctly accounts for the LLM warm-up asyncio.run call at line 756.

Comments Outside Diff (1)

  1. strix/interface/main.py, line 824-832 (link)

    P2 Silent exit on non-provider exceptions

    The handler catches all Exception subclasses, so a genuine code bug (e.g., AttributeError, TypeError) that reaches this block will also call sys.exit(1) — with no error output to the terminal, because the TUI error panel is only rendered for known provider errors. Before this change a raw traceback at least informed users (and developers triaging issues) what went wrong. If future exceptions are not caught and displayed upstream, this path silently discards all diagnostic information beyond what telemetry captures. Consider scoping the silent exit to known provider error types (e.g., openai.BadRequestError, anthropic.BadRequestError) and keeping raise for truly unexpected exceptions, or at minimum logging str(e) to stderr before exiting.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: strix/interface/main.py
    Line: 824-832
    
    Comment:
    **Silent exit on non-provider exceptions**
    
    The handler catches all `Exception` subclasses, so a genuine code bug (e.g., `AttributeError`, `TypeError`) that reaches this block will also call `sys.exit(1)` — with no error output to the terminal, because the TUI error panel is only rendered for known provider errors. Before this change a raw traceback at least informed users (and developers triaging issues) what went wrong. If future exceptions are not caught and displayed upstream, this path silently discards all diagnostic information beyond what telemetry captures. Consider scoping the silent exit to known provider error types (e.g., `openai.BadRequestError`, `anthropic.BadRequestError`) and keeping `raise` for truly unexpected exceptions, or at minimum logging `str(e)` to stderr before exiting.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
strix/interface/main.py:824-832
**Silent exit on non-provider exceptions**

The handler catches all `Exception` subclasses, so a genuine code bug (e.g., `AttributeError`, `TypeError`) that reaches this block will also call `sys.exit(1)` — with no error output to the terminal, because the TUI error panel is only rendered for known provider errors. Before this change a raw traceback at least informed users (and developers triaging issues) what went wrong. If future exceptions are not caught and displayed upstream, this path silently discards all diagnostic information beyond what telemetry captures. Consider scoping the silent exit to known provider error types (e.g., `openai.BadRequestError`, `anthropic.BadRequestError`) and keeping `raise` for truly unexpected exceptions, or at minimum logging `str(e)` to stderr before exiting.

Reviews (1): Last reviewed commit: "fix(interface): exit cleanly on unhandle..." | Re-trigger Greptile

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.

[BUG] Caido proxy tools not working in latest release – unhandled openai.BadRequestError crashes Strix

1 participant