Conversation
…h unknown The inject deferral line printed "pending input, dead-shell prompt, or unreadable pane" for every non-empty verdict, so a `pending` composer sent the first reader hunting a dead shell that did not exist. Each verdict now prints only what it means, including the absent and unrecognized cases. Also generalize the Unicode-whitespace boundary comment in the composer library from U+200B alone to the whole zero-width format class (U+200B, U+2060, U+FEFF), and state the two reasons the line sits there: mapping a zero-width character onto a space would invent separation the harness never drew, and widening `empty` is the unsafe direction while deferring is the one the max-defer alarm surfaces. Pin the boundary with regression coverage through the public classifier, under the ambient locale and LC_ALL=C: unicode padding reads empty, padding around real text stays pending, and zero-width chars defer.
Confidence Score: 5/5The PR appears safe to merge; no concrete changed-code defect or vision conflict was identified. The executable logging change covers the full composer verdict vocabulary and preserves safe deferral behavior, while the added tests and comments accurately pin existing locale-independent classification behavior. Reviews (1): Last reviewed commit: "fix(supervise): stop the composer deferr..." | Re-trigger Greptile |
|
Speaking as Kun's firstmate: first look on current class=corrective leftover. Issue #1988 was closed already-fixed on 8/23 via #2102 (
Main already maps U+00A0 (and the rest of Unicode White_Space outside ASCII) onto ASCII space before trim. This DIFF vs main: VISION.md: closing aligns. Away-mode already injects on a genuinely empty NBSP-padded composer; authority/scripts/restart/spine/vendor/scope are n/a for a leftover close. This HEAD: Land-eligible rec: NO (already on main via #2102). Captain-flag NOW: no. Closing as already fixed. This is not waiting-on-author and not a captain-decision. |
|
Speaking as Kun's firstmate: closing as already fixed on main by #2102. |
The bug
fm_composer_classify_contentclassifies a composer holding a prompt glyph followed by U+00A0 (non-breaking space) aspending, identically to real typed text.The glyph-stripping
casematches'❯ '*(glyph plus ASCII space), so an NBSP-padded row falls through to the single-character'❯'*branch and leaves\xC2\xA0behind.The following trim uses
[![:space:]], and bash's[:space:]does not match U+00A0 in the C/POSIX locale, so the NBSP survives, the row reads non-empty, and the verdict ispending.Why it matters
The away-mode daemon injects only on an affirmatively
emptycomposer.A Claude Code pane on the herdr backend renders its idle composer as
❯followed by U+00A0, verified from a raw ANSI capture:That pane can therefore never be injected into, so away-mode supervision is silently dead for it rather than degraded.
Observed on 2026-08-24: an overnight autonomous run stalled with 63,681 seconds (about 17.7 hours) of undelivered escalations.
Three workers completed and nothing after them started, because the wake path into the supervised session could not be used.
state/.supervise-daemon.logrepeated:The wedge alarm fired correctly and was the only signal that anything was wrong.
The fix
Treat Unicode whitespace as whitespace when judging composer emptiness, while keeping
pendingprotective of real input.A row of only invisible characters is empty.
Invisible characters around visible content remain
pending, so a half-typed line is still protected:Also fixed: the deferral log conflated two verdicts
state=pending: pending input, dead-shell prompt, or unreadable panenamed three conditions in one string.Per the classifier's own contract,
pendingmeans real unsubmitted text, while a dead shell or an unreadable pane isunknown.The conflated message sent the first reader looking for a dead shell that did not exist.
Each verdict now prints only what it actually means.
Tests
Extended
tests/fm-composer-lib.test.shrather than adding a runner.Cases cover the bare glyph, glyph plus NBSP, glyph plus ASCII space, glyph plus NBSP plus real text, and a row of only invisible characters.
Tests exercise the public interface and assert no implementation bytes.
bin/fm-lint.shpasses.