Skip to content

fix: batch of 7 CLI bug fixes from issue triage#569

Draft
raymyers wants to merge 9 commits intomainfrom
ray/many-fixes
Draft

fix: batch of 7 CLI bug fixes from issue triage#569
raymyers wants to merge 9 commits intomainfrom
ray/many-fixes

Conversation

@raymyers
Copy link
Contributor

@raymyers raymyers commented Mar 4, 2026

What changed

Seven standalone bug fixes identified during a triage pass of CLI_ISSUES_ROUGH.md. Each fix is its own commit; CLI_ISSUES_REPORT.md records the full triage (which issues are tracked, which are new, fix rationale).

Fix 4 – TUI corruption from setup_conversation prints

setup_conversation called print() directly, which interleaved raw bytes with Textual's TUI output. Replaced with Console(stderr=True) so setup logs are always written to stderr, never into the TUI.

Fix 10 – Conversation ID not shown in headless mode

Headless sessions gave no indication of which conversation was created, making it impossible to resume or correlate logs. Now prints Conversation ID: <id> immediately after creation.

Fix 11 – Headless summary missing cost

End-of-run headless summary omitted token usage and cost. Now includes:

Cost: $0.0031
Tokens: 1 204 in / 312 out

Fix 13 – Import-time DeprecationWarning noise

warnings.filterwarnings("ignore") and load_dotenv() were called after openhands_cli imports, so warnings fired during import. Moved both calls to the top of entrypoint.py, before any openhands_cli imports.

Fix 12 – System-prompt collapsible expands when default_cells_expanded=True

When the user enabled "expand all cells by default", the system-prompt collapsible (which can contain thousands of tokens) expanded too, flooding the conversation view. _create_system_prompt_collapsible now always passes collapsed=True regardless of the global setting.

Fix 2 – UnicodeDecodeError crash on non-UTF-8 AGENTS.md

Any skill file containing non-UTF-8 bytes (Latin-1, binary data) raised an unhandled UnicodeDecodeError in _build_agent_context, crashing the CLI on startup. Now catches the error, prints a helpful stderr warning with the byte offset, and continues with an empty skill list so the session still starts. (Root cause is in the SDK's Skill.load; this is a CLI-side guard.)

Fix 7 – Permission selector misfires on mouse click

InlineConfirmationPanel used a plain ListView whose _on_list_item__child_clicked immediately posted ListView.Selected, so a single accidental click anywhere in the list area could accept, reject, or escalate a permission prompt. Introduced _KeyboardOnlyListView: overrides _on_list_item__child_clicked to only update the highlighted index, never post Selected. Mouse clicks navigate; Enter confirms.


Commands run

make lint          # all checks passed
make test          # 1280 passed, 4 warnings

Tests were added for every behaviour change:

  • tests/test_entrypoint_warnings.py — AST-ordering + functional warning-suppression tests (Fix 13)
  • tests/tui/widgets/test_richlog_visualizer.py::TestSystemPromptCollapsible — 4 new tests (Fix 12)
  • tests/settings/test_skills_loading.py::TestAgentStoreBuildContextUnicodeError — 2 new tests (Fix 2)
  • tests/tui/panels/test_confirmation_panel.py — 2 new tests covering _KeyboardOnlyListView (Fix 7)

Before / After (Fix 7 — click misfire)

Before After
Single mouse click on any list item immediately accepted/rejected the pending action Mouse click moves the > highlight; user must press Enter to confirm

Checklist

  • Scope is minimal and focused on one change per commit
  • Tests added for every behaviour change
  • make lint
  • make test ✅ (1280 passed)
  • TUI touched (Fix 12, Fix 7) — unit tests cover the changed logic; no snapshot update needed (visual layout unchanged)

🚀 Try this PR

uvx --python 3.12 git+https://github.com/OpenHands/OpenHands-CLI.git@ray/many-fixes

Co-authored-by: openhands <openhands@all-hands.dev>
setup_conversation() called Console().print() directly to stdout while
the Textual TUI was running, producing 'Initializing agent...' and
'✓ Agent initialized with model: ...' lines interspersed with the UI.

Remove all three Console.print calls; the TUI surfaces agent status
through its own notification system.

Co-authored-by: openhands <openhands@all-hands.dev>
Previously the conversation ID was only printed after the session ended,
making it impossible to resume or inspect a long-running headless task
if the process was killed mid-way.

_initialize_main_ui() now calls _print_headless_conversation_id() in
headless mode before the agent starts running, printing both the hex ID
and a --resume hint to stdout.

Co-authored-by: openhands <openhands@all-hands.dev>
_print_conversation_summary() now reads conversation_state.metrics and
prints a Cost line (e.g. '$ 0.05  (↑ 1k ↓ 200 cache 10%)') when
metrics are available.  When metrics are None the output is unchanged.

Co-authored-by: openhands <openhands@all-hands.dev>
…orts

entrypoint.py called warnings.filterwarnings("ignore") AFTER importing
openhands_cli modules, so opentelemetry's module-level DeprecationWarning
was already emitted by the time the filter was applied.

Restructure the module so that:
1. .env is loaded (dotenv + stdlib only -- no heavy transitive deps)
2. DEBUG is checked and warnings.filterwarnings("ignore") is called
3. The heavy openhands_cli imports follow (warnings now suppressed)

noqa: E402 comments mark the intentionally-late imports.

Co-authored-by: openhands <openhands@all-hands.dev>
…etting

When default_cells_expanded=True the system-prompt collapsible expanded
fully, flooding the conversation view with thousands of tokens that are
rarely useful to read.

Force collapsed=True in _create_system_prompt_collapsible so that the
system prompt always starts collapsed, independent of the global
default_cells_expanded preference. Users can still expand it manually
if needed.

Co-authored-by: openhands <openhands@all-hands.dev>
AGENTS.md or other skill files with non-UTF-8 bytes (Latin-1, binary)
caused an unhandled UnicodeDecodeError in _build_agent_context, crashing
the CLI on startup rather than showing a useful message.

Wrap load_project_skills() in a try-except: on UnicodeDecodeError print a
warning to stderr identifying the encoding problem and the byte offset,
then continue with an empty skill list so the session can still start.

The underlying issue is in openhands/sdk (Skill.load opens files without
errors='replace'), but this CLI-side guard ensures users always get a
helpful message and a working session rather than a traceback.

Co-authored-by: openhands <openhands@all-hands.dev>
…ck misfires)

Clicking anywhere in the InlineConfirmationPanel's ListView area
immediately fired ListView.Selected, which could accept, reject, or
escalate permissions with a single accidental click on a high-DPI or
small terminal.

Introduce _KeyboardOnlyListView — a ListView subclass that overrides
_on_list_item__child_clicked to update the highlighted index WITHOUT
posting ListView.Selected. Mouse clicks now work as navigation (moving
the highlight to the clicked option); the user must press Enter to
actually confirm the selection.

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands_cli
   entrypoint.py1234563%23, 55–56, 59, 62, 64–65, 67–68, 71, 73, 75, 79, 84, 86–87, 136, 138–139, 142–146, 149–150, 152, 154, 164, 166–168, 170, 172–174, 177, 179, 183, 192, 194–196, 213, 260
   setup.py39976%65–68, 73–76, 84
openhands_cli/stores
   agent_store.py1882089%98, 116, 119, 158, 271–272, 275, 296, 477–478, 480, 487, 494, 496, 505, 508–509, 512–513, 515
openhands_cli/tui
   textual_app.py2344979%123, 259–260, 265, 268, 273, 284, 286–288, 308–309, 316, 324, 330, 334, 339–341, 418, 424, 481, 483, 520, 523, 538, 541, 543–544, 557–559, 561–567, 571, 579–580, 619, 621, 656, 677, 730–731, 760
openhands_cli/tui/panels
   confirmation_panel.py63198%145
openhands_cli/tui/widgets
   richlog_visualizer.py3599074%70, 73, 77, 81, 83, 173, 180–181, 193, 238, 279, 361, 411–414, 427, 430–431, 434–435, 438, 440, 443, 477–481, 485, 492–495, 499, 511, 546–548, 553–554, 557, 559–562, 564–567, 569–572, 574–576, 578, 586–588, 603–604, 606, 615–617, 636–637, 640, 717, 734, 736–738, 742, 780–781, 796, 802, 823–824, 828–830, 840–841, 845–847
TOTAL663092586% 

Pyright cannot narrow the type of `node` through a stored boolean
variable (`is_openhands_import = isinstance(...)`).  Inline the
`isinstance(node, ast.ImportFrom)` check directly in the `if` guard so
pyright narrows the type to `ast.ImportFrom` inside the block and
resolves `node.lineno` without error.

Fixes:
  tests/test_entrypoint_warnings.py:65 - reportAttributeAccessIssue (lineno)
  tests/test_entrypoint_warnings.py:67 - reportAttributeAccessIssue (lineno)

Co-authored-by: openhands <openhands@all-hands.dev>
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.

2 participants