Skip to content

Fix crash when resizing a terminal containing wide (CJK) characters#13041

Open
Im-Tae wants to merge 4 commits into
warpdotdev:masterfrom
Im-Tae:imtae/fix-cjk-resize-crash
Open

Fix crash when resizing a terminal containing wide (CJK) characters#13041
Im-Tae wants to merge 4 commits into
warpdotdev:masterfrom
Im-Tae:imtae/fix-cjk-resize-crash

Conversation

@Im-Tae

@Im-Tae Im-Tae commented Jun 25, 2026

Copy link
Copy Markdown

Description

Fixes a crash (SIGABRT / abort()) that quits Warp when a terminal containing
wide (full-width / CJK) characters — e.g. a Korean directory name shown in the
shell prompt — is resized (window/pane layout change, prompt redraw, etc.).

From the attached macOS crash report, the panicking frame is unambiguous:

warp::terminal::model::grid::resize::InitialCursorState::into_content_offset
  → core::result::unwrap_failed   (.expect("should have a content offset for point"))

Call chain:
TerminalView::resize_internal → TerminalModel::resize → BlockList::resize → GridHandler::resize → resize_storage → InitialCursorState::into_content_offset → panic

Root cause

During resize_storage, each cursor (current / saved / max) is converted from a
live-grid Point to a flat-storage content offset via
FlatStorage::content_offset_at_point. That function returns Err for a point
whose column/row does not map to tracked content (ColumnExceedsContent,
NonZeroColumnInEmptyRow, RowOutOfBounds, MissingGraphemeSizing). The
conversion unwrapped the result with .expect(...), so any such case aborted the
whole process instead of degrading.

Wide characters make this reachable: a wide char spans two columns but is a
single grapheme, so the grid's per-column accounting (which also counts wide-char
spacer cells) can diverge from flat storage's grapheme-run accounting. content_offset_at_point's own doc comment flags WIDE_CHAR_SPACER /
LEADING_WIDE_CHAR_SPACER cells as cases needing coverage, and resize_storage
already carries a maintainer TODO noting that max_cursor_point can end up with
"content after it" — i.e. in a position that does not line up with content.

Fix

into_content_offset no longer unwraps. The new content_offset_for_point
helper clamps the column back toward the start of the row until it finds a cell
that does have a content offset (column 0 of a non-empty row always resolves, so
this terminates), logging a warning. This mirrors the defensive cursor clamping
already present in InitialCursorState::new, and turns an app-killing abort into
a one-cell cursor placement difference in an already-anomalous state.

The sibling .expect("content offset should be valid") in
CursorContentOffset::into_cursor_point (the reverse conversion) is intentionally
left untouched to keep this change focused on the confirmed crash site; happy to
harden it in a follow-up if maintainers prefer.

Linked Issue

Fixes #9013
Fixes #8881

These are the same InitialCursorState::into_content_offset unwrap panic during
terminal resize, both already labeled ready-to-implement. #13040 (my report)
was triaged as a duplicate of these.

  • The linked issue is labeled ready-to-implement.

Testing

  • Added test_resize_with_wide_chars_does_not_panic in
    grid_handler_tests.rs, which drives mixed narrow/wide (CJK) prompt-and-output
    content through a reflow across a range of widths (including widths too narrow
    to hold a wide char in the trailing column) and asserts the resizes complete.
  • cargo test -p warp -- terminal::model::grid passes (full grid module).

Transparency on reproduction: I was not able to reproduce the exact
production content_offset_at_point Err in a unit test, because the in-memory
grid pads rows to full width with blank cells, which makes the column/empty-row
error paths unreachable from the public GridHandler API (verified by
instrumenting the conversion). The crash report nonetheless identifies the
.expect() site unambiguously, and the fix removes that abort. I'd appreciate a
maintainer sanity-check against Sentry telemetry for the exact triggering state.

  • I have manually tested my changes locally with ./script/run

Screenshots / Videos

before (crash)
https://github.com/user-attachments/assets/52b53e3c-81a6-44e0-b192-990206f50454

after (no crash)
https://github.com/user-attachments/assets/65a59338-de95-48e6-832f-f5c43cd2d8e4

Agent Mode

  • Warp Agent Mode

resize_storage converted each cursor to a flat-storage content offset and
unwrapped the result with `.expect()`. content_offset_at_point returns Err
for a cursor whose column doesn't map to tracked content — reachable with
wide (full-width / CJK) characters, where the grid's per-column accounting
diverges from flat storage's grapheme-run accounting — so resizing a terminal
showing CJK text (e.g. a Korean directory name in the prompt) aborted the app.

Clamp the cursor to the nearest preceding cell that has a content offset
instead of unwrapping, mirroring the defensive clamping already in
InitialCursorState::new. Add a regression guard exercising a wide-char reflow.
@cla-bot

cla-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have the users @Im-Tae on file. In order for us to review and merge your code, each contributor must visit https://cla.warp.dev to read and agree to our CLA. Once you have done so, please comment @cla-bot check to trigger another check.

@github-actions github-actions Bot added the external-contributor Indicates that a PR has been opened by someone outside the Warp team. label Jun 25, 2026
@oz-for-oss

oz-for-oss Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@Im-Tae

Every PR must be linked to a same-repo issue before Oz can review it.

This PR is linked to #13040, but no linked issue is marked ready-to-implement yet. Only repository maintainers apply that label, so please wait for a maintainer to mark the issue. Once it is marked, push a new commit or comment /oz-review to re-trigger review.

See the contribution guidelines for the full readiness model.

Powered by Oz

oz-for-oss[bot]
oz-for-oss Bot previously requested changes Jun 25, 2026

@oz-for-oss oz-for-oss Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@Im-Tae

Every PR must be linked to a same-repo issue before Oz can review it.

This PR is linked to #13040, but no linked issue is marked ready-to-implement yet. Only repository maintainers apply that label, so please wait for a maintainer to mark the issue. Once it is marked, push a new commit or comment /oz-review to re-trigger review.

See the contribution guidelines for the full readiness model.

Powered by Oz

@Im-Tae

Im-Tae commented Jun 25, 2026

Copy link
Copy Markdown
Author

/oz-review

@oz-for-oss

oz-for-oss Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@Im-Tae

I'm re-reviewing this pull request in response to a review request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

oz-for-oss[bot]
oz-for-oss Bot previously requested changes Jun 25, 2026

@oz-for-oss oz-for-oss Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overview

This PR hardens terminal grid resize cursor remapping so wide/CJK terminal content no longer aborts when content_offset_at_point cannot resolve the exact cursor cell, and it adds a regression test that resizes mixed-width content across several column counts.

Concerns

  • For this user-facing terminal behavior change, please include screenshots or a short screen recording demonstrating resize with wide/CJK characters working end to end. The PR description currently leaves manual testing unchecked and has an empty Screenshots / Videos section.

Verdict

Found: 0 critical, 1 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@Im-Tae

Im-Tae commented Jun 25, 2026

Copy link
Copy Markdown
Author

/oz-review

@oz-for-oss

oz-for-oss Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@Im-Tae

I'm re-reviewing this pull request in response to a review request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

oz-for-oss[bot]
oz-for-oss Bot previously requested changes Jun 25, 2026

@oz-for-oss oz-for-oss Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overview

This PR hardens terminal grid resizing when flat-storage cursor offset lookup fails for wide/CJK content, and adds a smoke-style resize test covering mixed-width text. Visual evidence is included in the PR description.

Concerns

  • The new fallback correctly searches backward for column-local lookup failures, but if every lookup in the row fails it returns ByteOffset::zero(). That offset can be invalid after scrollback truncation and can still panic during the reverse content_offset_to_point conversion.

Verdict

Found: 0 critical, 1 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Comment thread app/src/terminal/model/grid/resize.rs Outdated
@cla-bot

cla-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have the users @Im-Tae on file. In order for us to review and merge your code, each contributor must visit https://cla.warp.dev to read and agree to our CLA. Once you have done so, please comment @cla-bot check to trigger another check.

content_offset_for_point fell back to ByteOffset::zero() when no cell in
the cursor's row resolved to a content offset. Content offsets count every
byte the grid has ever seen, so the earliest tracked offset is generally
greater than zero (and grows as scrollback is truncated from the front);
a zero offset sits before the first stored row, which content_offset_to_point
rejects, re-introducing the very panic this function exists to prevent.

Walk backward over earlier rows as well as columns, snapping to the nearest
cell flat storage can actually resolve, so the fallback yields a valid offset
from a real row instead of a fabricated zero.
@Im-Tae Im-Tae force-pushed the imtae/fix-cjk-resize-crash branch from 92925a6 to 8e6ed2e Compare June 25, 2026 08:02
@cla-bot

cla-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have the users @Im-Tae on file. In order for us to review and merge your code, each contributor must visit https://cla.warp.dev to read and agree to our CLA. Once you have done so, please comment @cla-bot check to trigger another check.

@Im-Tae

Im-Tae commented Jun 25, 2026

Copy link
Copy Markdown
Author

@cla-bot check

@cla-bot

cla-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have the users @Im-Tae on file. In order for us to review and merge your code, each contributor must visit https://cla.warp.dev to read and agree to our CLA. Once you have done so, please comment @cla-bot check to trigger another check.

@cla-bot

cla-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

The cla-bot has been summoned, and re-checked this pull request!

@Im-Tae

Im-Tae commented Jun 25, 2026

Copy link
Copy Markdown
Author

@cla-bot check

@cla-bot cla-bot Bot added the cla-signed label Jun 25, 2026
@cla-bot

cla-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

The cla-bot has been summoned, and re-checked this pull request!

@Im-Tae

Im-Tae commented Jun 26, 2026

Copy link
Copy Markdown
Author

/oz-review

@oz-for-oss

oz-for-oss Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@Im-Tae

I'm re-reviewing this pull request in response to a review request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

oz-for-oss[bot]
oz-for-oss Bot previously requested changes Jun 26, 2026

@oz-for-oss oz-for-oss Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overview

This PR hardens terminal grid resize cursor remapping for wide/CJK content by replacing the direct content_offset_at_point(...).expect(...) with a backward search for a resolvable content offset, and adds a regression test that resizes mixed-width terminal content across several widths. The PR description includes before/after recordings, and there is no approved repository spec context to compare against.

Concerns

  • The new no-resolvable-cell fallback returns ByteOffset::zero() even though the helper documents that zero can be before the first stored row and will be rejected by the later content_offset_to_point(...).expect(...) conversion, preserving a panic path in the defensive branch.

Verdict

Found: 0 critical, 1 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Comment thread app/src/terminal/model/grid/resize.rs Outdated
// Flat storage tracks no resolvable cell at or before the cursor (e.g. it is
// empty). There is nothing to anchor the cursor to, so fall back to the
// start of content.
ByteOffset::zero()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ [IMPORTANT] This fallback contradicts the invariant above: if flat storage is empty or the first stored row starts after zero, into_cursor_point will call content_offset_to_point(ByteOffset::zero()).expect(...) and can panic again. Return a real stored offset or make the no-anchor case non-panicking before converting back.

The earlier fallback returned ByteOffset::zero() when no cell at or before
the cursor resolved to a content offset, contradicting the function's own
invariant. Flat storage that is empty, or whose first stored row begins after
zero (scrollback truncated from the front), has no valid offset of zero, so
content_offset_to_point would reject it and the .expect() in into_cursor_point
would panic again on resize.

Have content_offset_for_point return Option<ByteOffset> (None when nothing
resolves) and convert offsets back through resolved_point_or_origin, which
homes the cursor to the grid origin on a missing or unmappable offset instead
of unwrapping. This removes the last panicking path through the resize cursor
remap.
@Im-Tae

Im-Tae commented Jun 26, 2026

Copy link
Copy Markdown
Author

/oz-review

@oz-for-oss

oz-for-oss Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@Im-Tae

I'm re-reviewing this pull request in response to a review request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

oz-for-oss[bot]
oz-for-oss Bot previously requested changes Jun 26, 2026

@oz-for-oss oz-for-oss Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overview

This PR replaces the panic-prone cursor content-offset conversion during terminal grid resize with defensive clamping/fallback logic and adds a wide-character resize regression test. The PR description includes before/after recordings, and there is no approved spec context to compare against.

Concerns

  • The new no-anchor/unmapped-offset fallback can still flow through the AtCellAfterPoint adjustment, so it may advance from origin instead of homing the cursor as documented.

Verdict

Found: 0 critical, 1 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Comment thread app/src/terminal/model/grid/resize.rs Outdated
FinalCursorState::AtPoint(resolved_point_or_origin(grid, offset))
}
Self::AtCellAfterPoint(offset) => {
let mut point = resolved_point_or_origin(grid, offset);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ [IMPORTANT] This no-anchor fallback does not actually home AtCellAfterPoint to the origin: when resolved_point_or_origin returns { row: 0, col: 0 } for None or an unmapped offset, the unchanged cell-after logic below can advance it to col 1 or set input_needs_wrap. Return an AtPoint(origin) or otherwise distinguish fallback from a real anchor before applying cell-after semantics.

resolve_content_offset now returns Option<Point>: None when flat storage
holds no anchor, or when a concrete offset falls before the first stored row
and cannot be mapped. The AtPoint arm falls back to the origin, and the
AtCellAfterPoint arm returns AtPoint(origin) directly instead of feeding the
fabricated origin through the cell-after logic, which would otherwise advance
it to column 1 or set input_needs_wrap. This keeps the no-anchor case a true
cursor home and the real-anchor case unchanged.
@Im-Tae

Im-Tae commented Jun 26, 2026

Copy link
Copy Markdown
Author

/oz-review

@oz-for-oss

oz-for-oss Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@Im-Tae

I'm re-reviewing this pull request in response to a review request.

You can view the conversation on Warp.

I reviewed this pull request and requested human review from: @abhishekp106.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@oz-for-oss oz-for-oss Bot dismissed stale reviews from themself June 26, 2026 08:58

Oz no longer requests changes for this pull request after the latest automated review.

@oz-for-oss oz-for-oss Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overview

This PR hardens terminal grid resize cursor remapping for wide/CJK character content by replacing panic-on-missing-content-offset paths with defensive fallback resolution, and adds a regression test that resizes mixed ASCII/CJK terminal content across narrow widths.

Concerns

  • No blocking correctness, security, or spec-alignment concerns found in the attached diff.

Verdict

Found: 0 critical, 0 important, 0 suggestions

Approve

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@oz-for-oss oz-for-oss Bot requested a review from abhishekp106 June 26, 2026 08:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed external-contributor Indicates that a PR has been opened by someone outside the Warp team.

Projects

None yet

1 participant