A placeholder hint in an empty input box reads as the operator's draft, so the box gate holds every delivery forever #782
Open
opened 2026-10-05 19:39:20 +02:00 by ltms
·
3 comments
No Branch/Tag Specified
main
worker/778-12988a-4
worker/782-18f8bc-5
worker/780-faf58b-3
worker/759-authz-comment-and-role-list-e3f0e5-5
worker/756-758-observer-pane-discovery-7e6ffd-1
worker/759-role-model-comments-5d8409-3
worker/743-pane-discovery-ad5b75-5
worker/743-observer-send-4706db-6
worker/749-edge-baseline-28d1a0-3
worker/748-dead-comment-refs-f42ac5-4
worker/737-9c61d3-4
worker/737-a263f3-3
worker/737-20d1d9-1
worker/737-b038f7-2
worker/726-unit2-75cb13-4
worker/737-owner-key-ff061f-10
worker/736-presence-forget-f35144-9
worker/705-observer-14c258-6
worker/722-024c34-5
worker/726-ea34a0-2
worker/726-10cbf0-1
worker/729-5961c6-3
worker/727-ee14ed-3
worker/719-bdd95e-4
worker/702-4f5c7f-2
worker/715-5c43fc-1
worker/721-70f9ea-5
worker/718-99362b-2
worker/task-15-af0d10-12
worker/task-16-50a702-13
worker/task-12-4d0479-9
worker/task-13-823ce2-10
worker/705-ticket-owner-af9928-8
worker/703-list-collaborators-9c06c2-7
worker/669-example-truth-0b303d-6
worker/669-collab-deliverability-9ba859-3
worker/669-collab-reload-report-2a21bd-4
worker/669-7e80a6-1
worker/669-unit-d-efbbd7-1
worker/669-1b786a-1
worker/669-1d1d9f-1
worker/692-4afb9d-2
worker/689-02fced-13
worker/693-cf23fa-14
worker/677-fix-lead-collision-f69073-12
worker/638-fix-overmask-dbb1bf-11
worker/675-5b7478-4
worker/669-unit-a-70cc8f-3
worker/677-8cdaaf-5
worker/638-a7b391-1
worker/683-4536d6-2
worker/651-a75bbe-8
worker/680-20607d-7
worker/664-c12e95-3
worker/668-08534d-4
worker/672-0f2469-2
worker/670-7d1022-1
worker/661-ac7c28-2
worker/664-37fb9b-3
worker/663-remove-3arg-read-3f6783-1
worker/659-remove-dead-backcompat-ba5e6f-1
worker/637-revision-60a488-23
worker/656-redact-regression-tests-892903-19
worker/637-context-gauge-threshold-466eb5-16
worker/639-redact-line-numbers-de4ac4-17
worker/641-set-reformat-guard-6f96a4-18
worker/642-herdr-guard-scope-5de0e4-15
worker/650-javadoc-scope-95f3b3-14
worker/612-01e9f7-13
worker/612-a-r4-quarantine-outage-7ab0e8-5
worker/612-a-r9-r11-capacity-coverage-peers-cfcc79-7
worker/612-a-r10-loophealth-ccc872-8
worker/612-a-r12-turnregistrar-9e3bb7-9
worker/612-a-r5-leadconfigdir-9e70cf-6
lead/config-edit-redact-anchor-wording
worker/config-edit-seam-ca8dc1-1
worker/612-r67-630-lifecycle-290b8d-3
worker/629-625-ports-seams-da7d5d-4
worker/612-r12-exhaustion-f37cd7-1
worker/612-r38-amqp-24b083-2
worker/fleetd-612-unita-87807e-1
worker/612-b3-mcpwirings-da2b58-3
worker/612-b2-cb185-176d3a-2
worker/612-b1-completion-457459-1
worker/612-agaps-73a926-2
worker/608-sleeps-3a64ff-3
worker/621-b4520b-1
worker/618-b83894-2
worker/fleetd-615-e05481-5
worker/lead-autocompact-5f1ab2-3
worker/fleetd-613-f85deb-3
worker/fleetd-608-flaky-nudge-test-d0c2d1-3
worker/lead-context-gauge-ad404f-1
worker/gauge-wiring-9158c1-4
worker/redeploy-slowstart-ead0e5-5
worker/charter-bytes-13668c-6
worker/rollover-outcome-291483-2
worker/589-f64303-2
worker/593-1a8025-5
worker/589-fcd2aa-1
worker/568-9fdaa2-3
worker/571-attempted-outcome-5739f7-2
worker/581-completionresolver-cas-sites-0542b7-6
worker/562-loop-health-wiring-test-99611c-5
worker/562-surface-loop-health-7df5cc-4
worker/575-waiter-cleanup-sites-62ad80-1
worker/572-answer-lock-release-46a9ae-5
worker/567-probe-channel-leak-a38fc5-6
worker/551-record-before-send-7cbf56-1
worker/561-listener-fanout-survives-a-throw-61d538-2
worker/555-redeploy-main-flow-seam-65c2f5-2
worker/556-injector-owns-registration-e027a5-1
worker/552-post-restart-mktemp-abort-bc2672-4
worker/553-onstatus-completion-leak-0da881-2
worker/550-shasum-linux-196132-1
worker/538-loop-dies-on-error-4a5eeb-6
worker/426-health-coverage-ef1fd4-4
worker/504-failed-reported-clean-3cfd66-3
worker/537-capturedlog-close-e4c437-2
worker/459-broken-link-targets-cadc17-5
worker/535-appender-leak-fe74c1-1
worker/512-part2-shutdown-detection-434701-9
worker/529-logger-level-sweep-2a5533-8
worker/528-drain-gate-call-site-5de83d-7
charter/forge-mcp-vs-token
worker/521-swap-guard-unpinned-28e931-5
worker/519-probe-test-harness-d25ab8-4
worker/525-logger-level-leak-1b4eb0-6
worker/518-fleetmcp-resolver-wiring-8ef96c-1
worker/512-drain-complete-line-7edd71-3
worker/517-abort-branch-and-jar-id-41b641-2
worker/500-9e52c9-3
worker/509-4912f4-2
worker/511-9a4b23-1
worker/493-479f45-2
worker/505-03f8b2-1
worker/492-followup-detect-unclear
worker/501-a31fa0-7
worker/498-451d1c-5
worker/494-1015ce-2
worker/492-209647-1
worker/489-001902-2
worker/480-relative-handover-path-906323-1
worker/480-b-handover-skill-45bf1f-5
worker/474-followup-source-pin-f54a55-17
worker/474-charter-check-on-reload-f54a55-17
worker/466-quarantine-repeatcount-report
worker/393-opencode-skill-seeding-71854b-13
worker/469-canonical-tool-names-2a472a-16
worker/466-quarantine-escalation-5ae9c1-15
worker/446-hot-exhausted-pattern-0af580-6
worker/464-charter-tool-name-guard-a85635-12
worker/463-listfleet-default-fails-open-f1c76c-11
worker/458-invariant-5-by-purpose-862f9a-10
worker/439-coordinator-row-gate-bc032a-8
worker/449-herdr-protocol-576015-4
worker/450-abstract-spawn-599e1c-5
worker/437-ack-refuses-177d91-1
worker/444-placement-window-feb56a-2
worker/440-helddurable-derived-d462d7-13
worker/425-rework-placement-resolve-c58ba1-9
worker/421-lead-peek-held-msgs-cdbad2-10
worker/435-fixed-policy-cap-fe11de-12
worker/422-gate-state-observability-9e79d6-11
worker/431-memberregistry-live-readers-cdbad2-10
worker/424-architect-slot-hot-038b41-7
worker/422-model-gate-spawn-c29f48-6
worker/425-default-profile-live-f55534-8
worker/415-coverage-wording-2cbf9c-5
worker/416-3ad1da-1
worker/418-588283-3
worker/deterministic-stamp-race-409-3cb7b6-10
worker/armed-reads-live-config-404-ed931f-9
worker/reply-peer-refusal-391-5a34bd-7
worker/models-allowlist-aa9e9b-3
worker/ttl-stamp-race-399-f1122f-8
worker/scrub-receipt-400-316b3e-5
worker/exhaustion-detection-395-105105-6
worker/scrub-abort-394-316b3e-5
fix/scrub-uid-abort
worker/task-scrub-517574-2
worker/t386-clock-bd5b78-4
worker/t384-scrub-813790-5
worker/t381-cc-748314-2
worker/t373-336973-2
worker/t365-3920c5-3
worker/t358-6e989b-1
worker/t355-8b321c-1
worker/fleetd-369-hermetic-git-tests-e8b19a-3
worker/fleetd-368-stale-lead-binding-f5682e-2
worker/fleetd-360-deploy-units-0d3793-1
worker/359-dead-lead-tabs-f1253b-4
worker/362-worktree-skills-c03e51-3
worker/361-coord-visibility-655144-1
362-plugin-visibility-and-drift
worker/errscan-bed2ca-2
worker/amqp-log-identity-bed2ca-2
worker/withdefaults-guard-561704
worker/sleepguard-82076d-1
worker/fd334-9ee1b6-5
worker/fd348-f1ab27-4
worker/fd335-a71c35-1
worker/fd342-174a17-2
worker/fd345-490d0f-3
worker/fleetd-337-5ec7d4-21
worker/fleetd-341-af5a6b-24
worker/fleetd-339-5ca0a2-23
worker/fleetd-338-83a4a1-22
worker/fleetd-333-281f46-18
worker/fleetd-329-11bdbb-16
worker/fleetd-330-2770fb-17
worker/fix-326-50506e-15
worker/fix-324-3e9bbf-14
worker/fix-323-b8287d-13
worker/fix-316b-bd0860-11
worker/fix-318-76ca36-9
worker/fix-317-486aec-8
worker/fix-315-ce47c5-6
worker/fix-307-275890-6
worker/fix-308-b4f664-7
worker/fix-309-ec3939-8
worker/fix-310-7a3974-9
worker/fix-302-52ad0e-9
worker/fix-298-ce1acb-8
worker/fix-297-66bd11-7
worker/fix-296-104622-6
worker/fix-293-bare-closetab-eb22b5-3
worker/fix-280-gone-ask-lapse-bca98e-2
worker/fix-290-reapidle-guard-coverage-9b0dd1-1
worker/fix-285-trust-seed-8f3565-10
worker/fix-284-backend-error-seat-85912c-11
worker/fix-282-chained-ask-e6d0bb-8
worker/fix-283-teardown-leaks-f40dfa-9
worker/fix-281-pin-handler-actions-4921ac-7
worker/audit-rendezvous-lifecycle-d072ae-2
worker/audit-health-placement-1a2476-6
worker/audit-teardown-exits-e207a5-3
worker/audit-launcher-asymmetry-27e370-4
worker/audit-rest-authz-6ca53c-5
worker/investigate-275-abandon-asking-fdef52-8
worker/fix-274-worktree-leak-b0095d-7
worker/fix-273-exhausted-pattern-9665b5-6
worker/fleetd-267-model-check-bd8068-1
worker/fleetd-131-archunit-18b834-7
worker/fleetd-266-sshagent-rename-a014ff-6
worker/fleetd-184-uid-claim-8e1f31-4
worker/fleetd-184-warn-b381ee-10
worker/fleetd-184-docs-be1d12-9
worker/fleetd-257-9bf010-7
worker/fleetd-103-23a113-6
worker/fleetd-247-342356-5
worker/fleetd-116-04dea8-4
worker/fleetd-252-a830e0-3
worker/fleetd-111-7e8673-9
worker/fleetd-155c-f8ef4b-8
worker/fleetd-176-b928ca-3
worker/fleetd-249-7a7878-2
worker/cb248-composition-root-b-9acdf7-15
worker/cb148-envrc-default-fa6c82-12
worker/cb201-unit5-wiring-6c12e6-8
worker/cb241-fallback-echo-1175e9-11
worker/cb149-trust-dialog-2392a5-9
worker/cb134-148-overlay-visible-c9b986-10
worker/cb234-session-id-keyed-04e1fc-1
worker/cb201-unit3-nudge-abdf5c-6
worker/cb201-unit2-policy-c1102c-5
worker/cb201-unit4-outcome-a13bfa-7
worker/cb201-unit1-classifier-91b9b1-4
worker/cb201-227-refine-831980-3
worker/cb175-model-readback-0f085f-1
worker/cb222-charter-tmpdir-17f013-1
worker/cb226-architect-slot-race-cd3aa8-3
worker/cb224-worktree-root-group-024523-2
worker/cb-123-role-demotion-c600f7-2
worker/cb-219-opencode-roots-1f677e-1
worker/cb214-claude-session-id-b9eab4-4
worker/cb213-zdotdir-wrong-process-dd6de4-3
worker/cb211-exhaustion-classification-9546e0-2
worker/cb137-ambiguous-task-4df3d8-4
worker/cb209-agentsessionid-4dfdb6-2
worker/cb185-hostenvnames-2692b5-3
worker/cb206-opencode-sqlite-128718-2
worker/cb185-worktree-group-fc0c99-1
worker/cb-137-ask-ticket-e7760c-2
worker/cb-172-broker-uri-d36ae4-4
worker/cb-175-model-readback-76ead6-3
worker/cb-161-pane-ancestry-293510-1
worker/cb-164-rebase-885863-8
worker/cb-164-empty-scrape-false-success-1a80af-3
fix/cb-197-ticket-ttl-from-completion
worker/cb-189-remote-url-coverage-4692f3-1
worker/cb-185-blockers-027756-4
worker/cb-192-gap-log-11b631-2
worker/cb-633-fix-5f4396-3
worker/cb185-router-d6436d-3
worker/cb185-router-routing-gaps-9e9d33-3
worker/cb185-paneids-992586-2
worker/cb-633-allow-list-union-ed374b-1
worker/cb-157-credential-in-remote-url-496e44-2
worker/cb-641-health-herdr-evidence-8f1f54-6
worker/cb-640-health-msg-evidence-99c9cd-1
worker/cb-642-fleets-status-skill-bbbc40-5
cb-634-ide-mcp
worker/lead-comms-wiring-c014b9-7
worker/lead-mailbox-c19577-6
worker/autocompact-window-82bc2f-5
worker/cb-634-probe-18056f-4
worker/cb635-broker-urienv
worker/cb-632-config-retry-8e0efa-7
lead/cb-622e-claude-md
lead/cb-622-followup
worker/cb-622a-165dff-1
lead/cb-622d-opencode-mount
worker/cb-622b-717c67-2
worker/cb-622c-ab7759-3
worker/cb-617b2-20ca4b-3
worker/cb-617a-5c2f4a-1
worker/cb596-4e49ef-3
worker/cb586-10500c-1
worker/cb-606-b9343a-25
worker/cb604-1445f8-24
worker/cb582-477374-21
worker/cb584-8c2281-22
worker/cb600-e6b9a9-20
worker/cb602-ce257f-19
worker/cb601-b42837-18
worker/cb598-6c7ba7-17
worker/cb599-740fe4-16
worker/cb597-282224-15
worker/cb590fix-185e9a-10
worker/cb528-recovery-race
worker/cb594-96bead-8
worker/cb590-916766-2
worker/cb527-997d99-3
worker/cb592-env-leak-3cbf9c-1
worker/cb588-async-ticket-nudge-3218f7-5
worker/cb578b-9dcb13-6
worker/cb581-d24826-5
worker/m2-u5-ef8c42-15
worker/cb578a-516499-2
worker/cb576-01a04b-17
worker/cb579-lead-tab-acba06-20
worker/cb580-terminal-health-ed6058-21
worker/cb577-f36fdc-18
worker/cb573b-3db06f-16
worker/cb568c-f36fdc-18
worker/cb568-drop-cause-c3ac1c
worker/cb575-cancelled-notification-c3ac1c
worker/m4-sol-a2cbec-3
worker/cb574-async-ask-c3ac1c
worker/cb573-health-model-8ca857-14
worker/cb572-unknown-target-7f2e35-13
worker/u4-700706-9
worker/u3-b9fcb6-6
worker/u2-ef5b68-4
worker/u1-469dce-1-clean
worker/u1-469dce-1
worker/cb-564-health-events-70cf7e-2
worker/cb-565-recycle-drops-role-98e58f-3
worker/cb-563-missing-reply-df2866-1
worker/cb-562-readiness-gate-silent-6c23c9-3
worker/cb-560-architect-presence-da8155-1
worker/cb-561-architect-silent-off-a71cab-2
worker/cb-548-bind-architect-slot-fe1b8c-1
worker/parity-overlay-settings-5fb711-1
secrets-central-store
cb-559-hot-key-correction
cb-557-fleet-role-pools
worker/cb-553-maxload-explicit-spawn-305ee3-6
worker/cb-551-idle-lead-heartbeat-f1633c-1
worker/cb-544-drain-preserves-worktree-925fad-3
worker/cb-552-docs-sync-1cb9cf-4
worker/cb-548-rendezvous-guard-rebased
worker/cb-548-rendezvous-guard-116b53-10
worker/cb-548-authz-v2-586df6-8
worker/cb-548-authz-264363-5
salvage/cb-528b-codex-home
salvage/cb-528a-codex-launcher
CB-518-primary-flow
feature/peer-launcher-spi
cb-103-injector
v1.1.0
v1.0.0
Labels
Clear labels
blocked
needs-live-proof
ready-to-delegate
silent-default
Cannot start until something else lands. The body says what.
Merged and green, but never shown working on the running daemon. Not the same as done.
Scope, files and acceptance criteria are written. A worker can be briefed from the body alone.
A feature that compiles, passes tests, and ships turned off. Nine recurrences and counting.
No Label
Milestone
No items
No Milestone
Projects
Clear projects
No project
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: fleet/fleetd#782
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found by the operator, who said plainly: "nothing in its input box!!!!! its empty, something wrong" and then named the cause: "there are input hint inside the input box -> it is the confusion?" Yes. Measured against the deployed jar (
fleetd/run/fleetd.jar), with controls in both directions:Probe:
PromptBox.classifycalled directly on four synthetic panes, run as a single-file program in packagedev.ltms.fleet.herdragainst the live jar. The two EMPTY rows are negative controls — without them a broken probe returning DRAFT for everything would look like a finding.Mechanism
PromptBox.boxContenttakes the box line, drops the marker, then keeps every character that is not whitespace and not inCURSOR_GLYPHS. Anything left makes the readingDRAFT:So the detector cannot tell the operator's typing from text the TUI drew. The class javadoc already names this exact case as the accepted risk:
That reasoning was sound when a hint was hypothetical. It is now the live TUI's behaviour, and the chosen failure direction has a worse consequence than the one it was avoiding.
Why "hold rather than clobber" is the wrong trade once the hint is permanent
A clobber is one damaged line, visible immediately, and recoverable. A permanent hold silently kills the whole fleet channel for that pane, with one WARN after 20 holds and nothing after that. Observed on this host: a pane held deliveries 25+ consecutive times across two separate windows, while two peers' messages sat undelivered for over 20 minutes. The sender was told
acceptedandpending — worker workingthe entire time (#780).It is also undetectable from either side. The receiver sees an empty box and concludes the channel is broken; the sender sees success. Both peers on this host drifted onto Claude Code's
SendMessage, which has no box gate, without anyone deciding to.HOLD_WARN_STREAKdoes not rescue this: it makes the hold visible in the daemon log, which neither the sender nor the receiver reads.Proposed
fleet_list'spanesrow). A receiver that can see "3 queued, held: prompt box" fixes this in seconds.Related
pending — worker workingfor exactly these held messages, and a successful pane delivery logs nothing at all.BOX_MARKERSlist is["❯", "│ >"]; a previous ticket established that❯is the branch that fires on the live TUI and│ >matches nothing here. So marker detection is working; only the content classification is at fault.Not measured
The live hold at the time of writing was caused by real typed text, not a hint: the pane showed
❯ cleared the input boxand the daemon readDRAFT (19 characters)against my hand count of 18 non-whitespace. I cannot account for that one extra character, and it may indicate a glyph missing fromCURSOR_GLYPHS— worth checking, because if a bare cursor were counted then an empty box would never read EMPTY, and the control above shows it does. An earlier reading ofDRAFT (38 characters)on the same pane is consistent with a hint but I cannot prove it, because the pane content changed before I read it.Correction: the hint is a real mechanism, but the live box line carries a non-breaking space, and that is the measured defect
I chased the one unexplained character from the issue body (daemon
DRAFT (19)against my hand count of 18) and it is not a rounding slip. It is U+00A0, a no-break space, andboxContentcounts it as the operator's text.Measured by dumping the live box line's codepoints. Both idle lead panes on this host, read with
herdr pane read … --lines 40and classified by callingPromptBox.classifyon the capture against the deployed jar:So the TUI draws U+00A0 between the
❯marker and the text, while the gaps inside the typed text are ordinary U+0020. The classifier skips the U+0020 and keeps the U+00A0, which is the whole of the one-character discrepancy.The cause is a Java character-class detail, not a logic error:
Character.isWhitespaceexcludes the no-break spaces (U+00A0, U+2007, U+202F) by specification.boxContent's filter isCharacter.isWhitespace(c) || CURSOR_GLYPHS.indexOf(c) >= 0, so every no-break space reaches the content buffer.What that costs, with controls
The ASCII row is the negative control: the filter does work, for the space the TUI does not use.
Two consequences, and they are not equally proven:
DRAFTcount this gate reports is one too high on this TUI, because of the lead-in. Small, but it is a number in a log line that a human reads while diagnosing a dead channel, so it should be right.State.EMPTYbecomes unreachable and the pane never receives anything again. The synthetic row above shows the classifier's half of that. I have not measured the TUI's half, because no pane here currently has an empty box — both leads hold typed text.Evidence that the TUI does not pad an empty box today: gated deliveries have succeeded on this host (
task-368d62-7reachedwB:p1), and a padded empty box would have made that impossible. That is an inference from a successful delivery, not a direct reading of an empty box, and it is the thing to measure before sizing this.Does this change the fix?
It sharpens proposal 2 and adds a cheap one:
Character.isWhitespace(c) || Character.isSpaceChar(c)covers U+00A0, U+2007 and U+202F in one clause and needs no list to maintain. This is correct on its own terms — a no-break space is padding whatever draws it — so it is worth doing even though it is not today's outage.What is not the current cause
The holds observed on this host right now are real typed text:
wB:p1holdscleared the input boxandwA:p1holds a line startinggo. The hint mechanism in the issue body is real and reproducible, but no hint is on screen.wB:p1also shows← 1 agent, a live subagent, which makes itWORKING— a second, independent gate that an empty box would not clear.This is the real cause, and the signal to fix it exists. The gate reads the one source that throws it away.
This comment is newer than the brief and it wins. The operator said three times that the input boxes were empty. I twice read the glyphs, saw words, and reported them as the operator's typing. That was wrong both times. The boxes are empty. The words are Claude Code's placeholder hint, and it is drawn dim.
The measurement
herdr pane readtakes--format {text,ansi},--ansiand--raw, which the box gate does not use. With the escape codes kept, the box line is:\x1b[2mis SGR 2, faint. The whole of the text sits inside it.Across every pane on this host, read with
--source visible --ansi, taking the last box line of each:Every box line that carries text is dim. The five empty boxes carry no styling and no text, which is the negative control: an empty box with no hint is not being mis-styled into looking empty.
What I do not have is a positive control. No pane here currently holds real typed text, so I have not measured that typed text comes through without SGR 2. Do not ship the dim test without building that case — type a character into a pane and read it. If typed text were also dim, this test would classify a real draft as empty and clobber it, which is the failure this gate exists to prevent.
Why the gate cannot see any of this
PromptBox.PROBE_SOURCEis"detection", and that source strips every escape code. Byte counts from the same pane, same--lines 40, same moment:So on
detectionthe hint and a real draft are byte-identical apart from their words. No amount of care insideclassifycan separate them, because the distinguishing information is removed beforeclassifyis called. The class javadoc predicted this exact case and accepted it; the accepted risk has now happened.What this changes in your brief
Defect 1 (no-break space) stands as written — fix it.
Defect 2 (bound the hold) stands as written, and is still the fix that holds whatever the TUI does next. Build it.
Add defect 3, and treat it as the main one.
classifymust stop calling a dim box line a draft:AgentControl.read(target, source)takes the source name; check whether it can ask for ANSI at all, and if it cannot, that plumbing is part of this fix. If making that reachable needs a change outsidePromptBox.javaandPromptBoxTest.java, stop and tell me in your reply rather than editing a third file — two other workers are in this repo and I will take that change myself.EMPTY.One more reason not to lean on the dim test alone: it is a property of one TUI's styling, so it is a proxy, and the program that draws it is free to change it. The bounded hold is the part that does not depend on anyone else's rendering choices. Ship both, and make the bounded hold the thing that guarantees the channel recovers.
Correction to my own comment: the no-break space does NOT make EMPTY unreachable. The hint is the only blocker.
The trinotes pane started working between my two readings, its hint vanished, and that gave me the measurement I said I did not have. I had written that the no-break space could make
State.EMPTYunreachable. On the source the gate actually reads, it does not. I had only built that case synthetically and I should not have put it as high as I did.The same pane, empty box, no hint, read both ways:
Then
PromptBox.classifyon the detection capture:So herdr's
detectionsource trims the trailing no-break space when nothing follows it. The gate sees a bare marker and correctly reads the box as empty. That is why gated deliveries have been landing on this host all along — the inference I drew fromtask-368d62-7was right, and now it is measured rather than inferred.What the no-break space still is. It appears only when something follows it, so it is a
+1on everyDRAFTcount the gate logs, and nothing worse today. Worth fixing — a human reads that number while diagnosing a dead channel — but it is not the outage, and it must not be presented as one.Revised standing of the three defects:
DRAFTcount is one too highThe hint appears only while the pane is idle
The anki lead pointed out that the hint text in its own box was the exact sentence the operator had typed to it earlier, so the "hint" looked like its own last submitted prompt rather than fixed placeholder text. The trinotes transition supports that and adds the trigger.
Two readings of
wA:p1, minutes apart, nothing else changed by me:So the hint is drawn when the pane is idle and disappears when a turn starts. Taken with the anki observation, the hint is the pane's own last submitted prompt, shown dim while idle.
Treat that as a property of one TUI version, not a law. Both readings come from the same Claude Code build, so they are one instrument and not two independent confirmations.
It does explain the earlier varying counts (38, 31, 27 reported at different times) with no extra theory: the hint is whatever that pane last submitted, so its length changes with the conversation. No separate padding mechanism is needed to account for them.
It also narrows the blast radius. A pane only misses deliveries while it is idle and has submitted something before — which is exactly the state a pane sits in when it is waiting for a message. So the gate fails precisely when it is most needed, and recovers on its own the moment the pane gets busy, which is when delivery is correctly refused anyway. That is the worst possible pairing and it is why this looked intermittent.
Nothing changes in what to build
Defect 3 is still the fix, defect 2 is still the guarantee, defect 1 is still worth the one clause. The positive control I asked for in my previous comment is still missing and still required: I have not measured that real typed text arrives without SGR 2. Until someone types a character into a pane and reads it back, the dim test must sit behind the bounded hold, not in front of it.