Skip to content

fix(cli): handle click.Abort to show 'Aborted.' instead of empty unexpected error (#13062) - #13063

Merged
kodjima33 merged 1 commit into
BasedHardware:mainfrom
armorbreak001:fix/cli-abort-empty-error
Sep 8, 2026
Merged

fix(cli): handle click.Abort to show 'Aborted.' instead of empty unexpected error (#13062)#13063
kodjima33 merged 1 commit into
BasedHardware:mainfrom
armorbreak001:fix/cli-abort-empty-error

Conversation

@armorbreak001

@armorbreak001 armorbreak001 commented Sep 8, 2026

Copy link
Copy Markdown

Summary

Fixes #13062

Bug: Interrupting an interactive prompt (e.g. omi auth login method selection) with Ctrl-C prints omi: unexpected error: `` (empty message) and exits 1, instead of the intended Aborted.` with exit code 130.

Root cause: Click raises click.Abort on prompt interruption. In some Click versions, click.Abort does not inherit from click.ClickException, so it falls through the exception ladder past the ClickException handler and hits the generic Exception handler — which formats it as an empty "unexpected error".

Change

One-line fix in sdks/python-cli/omi_cli/main.py: add click.Abort to the existing except (KeyboardInterrupt, EOFError) clause.

- except (KeyboardInterrupt, EOFError):
+ except (KeyboardInterrupt, EOFError, click.Abort):

This matches the documented intent in the comment above the handler: "KeyboardInterrupt / EOFError — Ctrl-C / Ctrl-D. Conventional 130."

Testing

  • Manual: ran omi auth login, pressed Ctrl-C at the method-selection prompt → prints Aborted. and exits 130
  • Syntax check passes (ast.parse)
  • The existing test suite in tests/test_main.py (if present) should continue to pass since this only adds an exception type to an existing handler

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file

Confidence score: 4/5

  • sdks/python-cli/omi_cli/main.py changes click.Abort handling from an empty unexpected-error/exit 1 response to Aborted./exit 130, so CLI behavior could regress without coverage; add a tests/test_main.py regression test that exercises main() with app() raising click.Abort.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="sdks/python-cli/omi_cli/main.py">

<violation number="1" location="sdks/python-cli/omi_cli/main.py:238">
P2: This fix changes observable behavior (empty "unexpected error"/exit 1 → "Aborted."/exit 130) but adds no regression test in tests/test_main.py. Add a test that calls main() with app() raising click.Abort (via monkeypatch) and asserts exit code 130 and "Aborted." on stderr, so the fix isn't silently lost.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread sdks/python-cli/omi_cli/main.py Outdated
except typer.Exit as exc:
sys.exit(exc.exit_code)
except (KeyboardInterrupt, EOFError):
except (KeyboardInterrupt, EOFError, click.Abort):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This fix changes observable behavior (empty "unexpected error"/exit 1 → "Aborted."/exit 130) but adds no regression test in tests/test_main.py. Add a test that calls main() with app() raising click.Abort (via monkeypatch) and asserts exit code 130 and "Aborted." on stderr, so the fix isn't silently lost.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At sdks/python-cli/omi_cli/main.py, line 238:

<comment>This fix changes observable behavior (empty "unexpected error"/exit 1 → "Aborted."/exit 130) but adds no regression test in tests/test_main.py. Add a test that calls main() with app() raising click.Abort (via monkeypatch) and asserts exit code 130 and "Aborted." on stderr, so the fix isn't silently lost.</comment>

<file context>
@@ -235,7 +235,7 @@ def main() -> None:
     except typer.Exit as exc:
         sys.exit(exc.exit_code)
-    except (KeyboardInterrupt, EOFError):
+    except (KeyboardInterrupt, EOFError, click.Abort):
         sys.stderr.write("\nAborted.\n")
         sys.exit(130)
</file context>

Comment thread sdks/python-cli/omi_cli/main.py Outdated
@Git-on-my-level

Copy link
Copy Markdown
Collaborator

Thanks @armorbreak001 — nice diagnosis and a clean first contribution.

I verified the root cause: click.Abort subclasses RuntimeError (not click.ClickException) and str(click.Abort()) is empty, which is exactly why it slipped past the except click.ClickException rung (main.py:231) and landed in the last-chance handler (main.py:241), producing the empty omi: unexpected error: message with exit 1 reported in #13062. Adding it to the except (KeyboardInterrupt, EOFError, click.Abort) rung at main.py:238 is the right placement — it inherits from neither ClickException nor typer.Exit, so it cannot shadow the rungs above it — and import click is already present at main.py:23. The 130 exit code matches the documented convention for that rung.

Two small follow-ups, neither blocking:

  1. sdks/python-cli/tests/test_main.py currently covers version/help/config paths but has no exception-ladder coverage. A tiny regression test (monkeypatch app to raise click.Abort, assert exit 130 and Aborted. on stderr) would keep this fix from being silently lost.
  2. The main() docstring's ladder description (main.py:207-218) and the comment above the handler still mention only KeyboardInterrupt / EOFError — worth updating to include click.Abort so the docs stay accurate.

The fix itself is correct as-is and resolves the reported behavior. Leaving the merge call to a human maintainer.

This review was produced by the repository's automated review assistant on behalf of the maintainers.


by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with need human response.

@Git-on-my-level Git-on-my-level added needs-tests PR introduces logic that should be covered by tests python labels Sep 8, 2026
@LNLenost

LNLenost commented Sep 8, 2026

Copy link
Copy Markdown

Thanks for picking up #13062. I had already prepared the same handler fix locally while investigating the report; I will not open a competing implementation PR.

For the requested regression coverage, here is our tested patch. It exercises the real main() entry point, login command and Click prompt with controlled stdin, rather than replacing app() with an exception. Both Ctrl-C and EOF cases fail with the old handler (exit 1 instead of 130) and pass with click.Abort handled. Targeted suite: 8 passed; full CLI suite: 130 passed, 1 Windows-only skip on Linux ARM64/Python 3.14.5. We also reproduced SIGINT at the prompt in a PTY subprocess (exit 1 before, 130 after). No live authentication or credentials used.

diff --git a/sdks/python-cli/tests/test_main.py b/sdks/python-cli/tests/test_main.py
index edc2275..285c2e5 100644
--- a/sdks/python-cli/tests/test_main.py
+++ b/sdks/python-cli/tests/test_main.py
@@ -2,12 +2,36 @@
 
 from __future__ import annotations
 
+import io
 import json
 
+import pytest
+
 from omi_cli import __version__
 from omi_cli.main import app
 
 
+@pytest.mark.parametrize("interruption", [KeyboardInterrupt, EOFError])
+def test_login_prompt_interruption_exits_cleanly(config_path, monkeypatch, capsys, interruption) -> None:
+    from omi_cli.main import main
+
+    class InterruptedInput(io.StringIO):
+        def isatty(self):
+            return True
+
+        def readline(self, *args, **kwargs):
+            raise interruption
+
+    monkeypatch.setattr("sys.stdin", InterruptedInput())
+    monkeypatch.setattr("sys.argv", ["omi", "auth", "login"])
+    with pytest.raises(SystemExit) as exc:
+        main()
+    assert exc.value.code == 130
+    stderr = capsys.readouterr().err
+    assert "Aborted." in stderr
+    assert "unexpected error" not in stderr
+
+
 def test_version_flag(cli_runner) -> None:
     result = cli_runner.invoke(app, ["--version"])
     assert result.exit_code == 0

Feel free to incorporate this coverage into this PR. This investigation and test contribution are AI-assisted by Hermes on behalf of @LNLenost. The optional bounty request on #13062 remains unapproved; this comment does not claim an award or payment.

@armorbreak001

Copy link
Copy Markdown
Author

Thanks @LNLenost! Really appreciate you sharing the regression test patch — I'll integrate it into this PR shortly. The handler fix was straightforward but having proper test coverage through the real main() entry point is exactly what's needed for a complete fix. I'll review your suggested approach and make sure it's included before the next update. 🙏

…Abort

Add parametrized tests exercising the real main() entry point with
KeyboardInterrupt and EOFError raised at an interactive login prompt.
Verifies 'Aborted.' on stderr, exit code 130, and no 'unexpected error'
leak. Patch contributed by @LNLenost; handler rung for click.Abort
included (RuntimeError subclass falls past KeyboardInterrupt handler).
@armorbreak001
armorbreak001 force-pushed the fix/cli-abort-empty-error branch from 266d761 to 0da9310 Compare September 8, 2026 13:22
@armorbreak001

Copy link
Copy Markdown
Author

Integrated — thanks again @LNLenost! 🙏

What's in the update:

  • Added your parametrized regression test (KeyboardInterrupt + EOFError at the login prompt) to tests/test_main.py, exercising the real main() entry point with a fake TTY stdin
  • The fix branch also includes the explicit except click.Abort rung in the exception ladder (with a comment explaining the RuntimeError subtlety), plus an updated docstring for the ladder order

Verification:

  • Targeted: test_main.py → 8 passed
  • Full CLI suite: 127 passed, 1 skipped (Windows-only) on Python 3.12/Linux

Ready for re-review whenever you have a moment.

@kodjima33 kodjima33 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug fix: click.Abort not caught by KeyboardInterrupt/EOFError handler on some Click versions, prints empty unexpected-error instead of 'Aborted.'; confirmed still present on main. One-line fix, linked #13062, tests added. Confidence 4/5.

@kodjima33
kodjima33 merged commit ef31972 into BasedHardware:main Sep 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-tests PR introduces logic that should be covered by tests python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python CLI login cancellation reports an empty unexpected error

4 participants