fix: the picker panicked when TERM named no usable terminal - #647
Merged
Merged
Conversation
skim's terminal setup looks up the terminfo entry TERM names and unwraps the result. A real terminal with TERM unset (env -i, a docker exec -t that sets none), or naming an entry the machine lacks (a terminal's own name in a container without its terminfo), aborted `dl stop`, `dl rm` and `aid resume` with a panic before a row was drawn. The picker now checks the entry first. When it cannot be used, it swaps TERM for xterm-256color while skim runs, which always resolves through the term crate's ANSI fallback, and puts the old value back afterwards so the session a pick opens inherits the user's TERM. One line says what was done once the picker has given the screen back. Claude-Session: https://claude.ai/code/session_01DiHLcVgAuJAMdsS4skh48w
…able term 0.7 answers any xterm*, screen* or tmux* name that has no database entry with a built-in ANSI entry that holds colours and no cup, and skim-tuikit writes nothing for a capability an entry lacks. So TERM=xterm-kitty in a container without that entry drew a garbled picker, and with no terminfo database at all the xterm-256color fallback was that same entry while the note said the picker was drawn as xterm-256color. The choice is now made on whether the entry has cup. When neither TERM's entry nor xterm-256color's has it, skim is not started, and dl and aid resume say why and how to name the workspace instead. Claude-Session: https://claude.ai/code/session_01DiHLcVgAuJAMdsS4skh48w
TermInfo::from_env reads TERM with env::var, which answers nothing for bytes that are not UTF-8, so its TermUnset came back for a TERM that was set, and the note called it unset while the restore put the bytes back. TERM is now read once, with var_os, into a Was that both the note and the restore use. from_env only decides whether the entry can draw. Claude-Session: https://claude.ai/code/session_01DiHLcVgAuJAMdsS4skh48w
Deleting the restore in DrawableTerm's drop left every test passing, because the fake devpod logged its arguments and nothing of its environment. It now also logs the TERM each call other than list ran under, to a second file so the exact matches on the call log are untouched, and the TERM test asserts the stop ran under the user's own TERM and nothing ran under xterm-256color. With the restore deleted it fails. Also: a quit picker under a swapped TERM still gives the note and stops nothing, and a TERM that can draw is not swapped. Claude-Session: https://claude.ai/code/session_01DiHLcVgAuJAMdsS4skh48w
Reviewer's GuideThe PR prevents skim from panicking or drawing a garbled picker for missing or unusable Sequence diagram for safe picker fallback and TERM restorationsequenceDiagram
participant Command
participant Picker as select::pick
participant Terminfo
participant Skim
participant Session
Command->>Picker: pick(workspaces, arity, cache_dir)
Picker->>Terminfo: TermInfo::from_env()
Terminfo-->>Picker: missing or non-drawable TERM
Picker->>Terminfo: TermInfo::from_name(xterm-256color)
Terminfo-->>Picker: drawable fallback
Picker->>Picker: DrawableTerm::swap()
Picker->>Skim: run_skim()
Skim-->>Picker: selected workspace or escape
Picker->>Picker: DrawableTerm::drop()
Picker->>Picker: restore original TERM
Picker-->>Command: Pick result
Command->>Session: open selected workspace
Session->>Session: inherit original TERM
State diagram for picker terminal outcomesstateDiagram-v2
[*] --> CheckTERM
CheckTERM --> RunPicker: current terminfo has cup
CheckTERM --> CheckFallback: current entry missing or lacks cup
CheckFallback --> FallbackPicker: xterm-256color has cup
CheckFallback --> Undrawable: fallback also lacks cup
FallbackPicker --> RestoreTERM
RunPicker --> Result
RestoreTERM --> Result
Undrawable --> ExplicitCommand
Result --> [*]
ExplicitCommand --> [*]
Flow diagram for terminal validation before opening the pickerflowchart TD
A[select::pick] --> B[TermInfo::from_env]
B --> C{drawable current entry?}
C -->|Yes| D[run_skim]
C -->|No| E[TermInfo::from_name xterm-256color]
E --> F{drawable fallback entry?}
F -->|Yes| G[DrawableTerm::swap]
G --> D
F -->|No| H[Pick::Undrawable]
D --> I[DrawableTerm::drop]
I --> J[Restore original TERM]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Codecov Report❌ Patch coverage is
Additional details and impacted files
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
The Pick::Undrawable arms in render_select and pick_one had no test. They are reachable: with TERMINFO_DIRS set, the term crate searches only that list, so an empty directory leaves even the xterm-256color fallback with the built-in ANSI entry, which has no cup. picker.rs runs `dl stop` and `dl` under that environment and checks the refusal line each verb gets, exit 1, no alternate screen, and no devpod call past the listing. interactive.rs does the same for `aid resume`. Both wait for the exit against a deadline, so a picker that opens anyway fails the test in place of hanging it. Claude-Session: https://claude.ai/code/session_01DiHLcVgAuJAMdsS4skh48w
The draw works without smcup, but the entry then lacks rmcup as well, so skim's pause writes nothing to restore the screen and the picker's rows stay on it. The comment now says so. Claude-Session: https://claude.ai/code/session_01DiHLcVgAuJAMdsS4skh48w
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The workspace picker panicked when
TERMnamed no usable terminal. This PR fixes that. The picker is the one thatdl stop,dl rmand the other verbs open when you name no workspace. #646 also uses it foraid resume.skim draws the picker. Before it draws, it looks up the terminfo entry that
TERMnames, and it callsunwrap()on the result (Skim::run_within skim 0.20.5). So these real terminals ended the command with a panic before any row was drawn:TERMis unset. Examples areenv -i, or adocker exec -tthat sets noTERM.TERMnames an entry that this machine does not have. An example is a terminal's own name, such asxterm-ghostty, in a container without its terminfo.A second case did not panic, but it drew a garbled picker. For any name that starts with
xterm,tmux,screen,rxvtand a few others, thetermcrate returns a small built-in entry when the database has none. That entry has colours but no cursor movement (cup), so skim cannot put the rows in place.What this changes
select::picklooks up the entry forTERM. It then makes one of three decisions.planinrust/dl/src/select.rsmakes the decision, and it is a pure function:xterm-256colorcan, the picker setsTERM=xterm-256colorwhile skim runs.Pick::Undrawablearm ends the command with the reason and the command to type instead:dl <workspace> <verb>for dl, andaid resume <workspace>for aid.TERMcomes back. When skim returns, the picker puts the old value back, or removesTERMif it was unset. So the session that a pick opens gets your ownTERM, notxterm-256color. One line on stderr says what the picker did, for exampleTERM is unset, so the picker was drawn as xterm-256color.TERM. The note and the restore use the samevar_os("TERM")value. So aTERMwith bytes that are not UTF-8 is not reported as unset.term = "0.7"is now a direct dependency ofdl. skim already uses this version, soCargo.lockgets one new line and no new crate.How I checked it
rust/dl/tests/picker.rsrundl stopagainst the fake devpod withTERMunset,TERM=no-such-terminalandTERM=xterm-no-such-entry. Each run picks a row and checks thatstop blooop-wayfinderreaches devpod, and that the note is shown. Before the fix, the first two runs panicked and drew nothing. The third drew with the stripped entry and printed no note.TERMof each call in a second log. The test checks that thestopcall ran with your ownTERM. I deleted the restore to test the test, and the test then failed withTERM=xterm-256color stop blooop-wayfinder.TERMunset still prints the note and stops nothing, and a goodTERMprints no note. Unit tests cover the three outcomes ofplan.TERMINFO_DIRSis set, thetermcrate searches only that list. So the tests set it to an empty directory, and then evenxterm-256colorcannot move the cursor.dl stop,dlandaid resumeeach stop with the line to type, exit 1, and send nothing to devpod butlist.cargo fmt --check,cargo clippy --locked --all-targets -- -D warnings,cargo test --locked --workspace(2702 passed, 0 failed), the Python suite (828 passed) andprek run --from-ref origin/main --to-ref HEAD.Review notes
dl <workspace> rm, notdl <workspace> rm --force, so it does not repeat the flags you typed.cupbut nosmcupalso has normcup. skim can draw with it, but the rows stay on the screen when the picker closes.set_varcalls areunsafe. The comments give the reason that they are safe here: no other thread indlreads the environment while the picker runs, and skim joins its input thread before it returns.🤖 Generated with Claude Code
Summary by Sourcery
Make workspace selection robust when the current terminal lacks usable terminfo capabilities.
Bug Fixes:
TERMis unset or names an unavailable or unusable terminal.TERMafter picker interaction so subsequently opened sessions inherit the user’s terminal setting.Enhancements:
xterm-256colorfor drawable picker sessions.termdependency support for terminal capability checks.Documentation:
Tests:
dlandaid resume.