Skip to content

fix(supervision): treat NBSP as whitespace in composer detection#78

Open
wyattwalter wants to merge 1 commit into
kunchenguid:mainfrom
wyattwalter:fix/composer-nbsp
Open

fix(supervision): treat NBSP as whitespace in composer detection#78
wyattwalter wants to merge 1 commit into
kunchenguid:mainfrom
wyattwalter:fix/composer-nbsp

Conversation

@wyattwalter

Copy link
Copy Markdown

Problem

The composer-state detector (fm_tmux_composer_state in bin/fm-tmux-lib.sh) misclassifies an idle Claude composer as holding pending input. Two consequences:

  • The away-mode sub-supervisor's composer guard defers every escalation, so /afk silently delivers nothing for the entire away period (the durable queue preserves it, but the proactive notify path is dead). This is the afk-invx-i5 failure mode reopening after a TUI rendering change.
  • fm-send.sh shares the detector, so it reports a false "Enter swallowed; text left in composer" on every steer into an idle pane, even when the message submitted fine.

Root cause

The current Claude TUI pads its prompt with a non-breaking space (U+00A0) rather than an ASCII space, rendering the idle composer row as ❯<U+00A0>. The detector strips the box-border glyphs and then trims whitespace with POSIX [:space:], which does not match U+00A0. The trailing NBSP survives the trim, so stripped is ❯<U+00A0> rather than a bare , never matches the bare-prompt "empty" case, and falls through to pending.

Reproduced live: fm_tmux_composer_state returns pending for known-idle Claude panes; the stripped value's hex is e2 9d af c2 a0 ( + U+00A0).

Fix

Normalize U+00A0 to an ASCII space before the trim, alongside the existing border-glyph strips. After the fix, the same idle panes classify empty.

Minimal and harness-generic; does not affect busy detection or genuine pending input.

The claude composer pads its prompt with a non-breaking space (U+00A0),
which POSIX [:space:] does not match. An idle "❯<NBSP>" prompt therefore
survived the trim in fm_tmux_composer_state, never matched the bare-prompt
"empty" case, and classified as pending input. This wedged the away-mode
daemon (the composer guard deferred every escalation) and tripped fm-send's
swallowed-Enter check on every idle pane. Normalize U+00A0 to a space
before trimming.
@wyattwalter

Copy link
Copy Markdown
Author

Note on portability — why this may not reproduce everywhere:

The trim in fm_tmux_composer_state uses POSIX [:space:], and whether that matches the non-breaking space (U+00A0) is decided by the C library's character classification under the active locale — it is not universal. Tested on glibc:

  • C.UTF-8: NBSP survives the trim → bug present
  • C: NBSP survives the trim → bug present

So on glibc, neither locale strips the NBSP. BSD libc (macOS) and some fuller locales may classify U+00A0 as whitespace, in which case the trim already catches it and the symptom never appears. The NBSP padding itself comes from the Claude TUI (the same binary across platforms), so the trigger is platform-independent; only whether the trim catches it varies by locale/libc. That asymmetry is probably why it can sit unnoticed depending on the dev environment.

The fix is deliberately locale-independent: it replaces the literal UTF-8 bytes (\xc2\xa0) rather than relying on [:space:] classification, so it is correct on any locale, glibc or BSD libc, and works back to bash 3.2 (the system bash on macOS).

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.

1 participant