Skip to content

infinity-agent-cli: display truncation slices strings by byte index, can panic on non-ASCII #118

Open

Description

@MingweiSamuel

Follow-up to string-related review comments on #115. These byte-index slicing sites pre-date that PR (they exist on main both before and after the change), so they are collected here to be fixed in a separate PR.

Rust string slicing like &s[..n] operates on byte indices and panics if n is not a char boundary. All of the sites below truncate display strings this way. Thread IDs are currently ASCII UUIDs, but with remote-prefixed thread refs (remote/uuid, see #115) a remote name — and session titles already today — can contain non-ASCII, making these potential runtime panics.

Locations

  1. crates/infinity-agent-cli/src/session_picker.rs — session name truncation (both the title and id branches):
let name = if let Some(ref title) = info.title {
    if title.len() + extra_len > name_width {
        let trunc = name_width.saturating_sub(extra_len + 1);
        format!("{}…{}{}", &title[..trunc], remote_label, current_suffix)
    ...
} else {
    let id = id.to_string();
    if id.len() + extra_len > name_width {
        let trunc = name_width.saturating_sub(extra_len + 1);
        format!("{}…{}{}", &id[..trunc], remote_label, current_suffix)
    ...

The title branch is reachable with non-ASCII today (user-provided session titles). Review comment: #115 (comment)

  1. crates/infinity-agent-cli/src/terminal.rs — subscription-event prefix for child threads:
let rendered = child_tid.to_string();
format!("[{}] ", &rendered[..rendered.len().min(8)])

Review comment: #115 (comment)

  1. crates/infinity-agent-cli/src/terminal.rs — short thread id in the status/help line:
let tid = tid.to_string();
let short_id = if tid.len() > 8 { &tid[..8] } else { tid.as_str() };

Review comment: #115 (comment)

Suggested fix

Use a char-safe (or display-width-aware) truncation instead of byte slicing, e.g.:

  • s.chars().take(n).collect::<String>(), or
  • &s[..s.floor_char_boundary(n)] (stable since Rust 1.93), or
  • width-based truncation via unicode-width for correct terminal column alignment (note: the surrounding layout math in session_picker.rs also mixes str::len() byte lengths with chars().count() when computing label column ranges).

A small shared helper (e.g. fn truncate_display(s: &str, max: usize) -> &str) in infinity-agent-cli would cover all three sites.

Activity

  1. kaluli123123 commented on Nov 3, 2026

    @kaluli123123

    Implemented in draft PR #180 (commit 61ae475854b9eaf6f2a6e35ff117aebd590ad24d).

    • Root cause: Arbitrary byte slices in the session picker and terminal short IDs could split UTF-8; remote label offsets mixed bytes and characters.
    • Fix boundary: Keep every reported display slice on a UTF-8 boundary and calculate remote label offsets in character units.
    • Focused checks: CLI library 17 passed; tui_flow_snapshots 15 passed, including 3 new regression tests (the picker test covers 3 cases).
    • Runtime proof: cargo-run temporary example invoked the public terminal loop against a virtual terminal: status 1234567 | /help, child subscription [1234567] ⚡watch: changed, picker 123456….
    • Full all-target test run: 358 passed, 7 failed, 0 ignored. The exact same seven TUI snapshots fail on clean base 1abc6fcff1f186124f924b56aa92c3a3684c90c5 (355 passed, 7 failed). No baselines or tolerances were changed.
    • Doctests: 45 passed, 1 ignored. Rust formatting, workspace Clippy with warnings denied, and all-target compilation passed. Independent read-only review found no blocking issues.

    I am maintaining this proposed fix. The PR remains Draft, not ready or merged: full checks are not green, remote CI currently lists no checks, and maintainer review remains outstanding. The repository script skipped docs/UI Prettier because node_modules are absent; optional browser e2e and THIRD-PARTY verification were not reached after the test gate failed. No live model account was used. No merge, Issue closure, or deployment has been performed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions