A lead nudge pastes AND submits, so it clobbers the operator's half-typed prompt — the injectable gate cannot see a draft #761
Closed
opened 2026-10-05 10:23:07 +02:00 by ltms
·
2 comments
No Branch/Tag Specified
main
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#761
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?
Reported by the operator: "fix the issue when I typing something in lead session and fleetd also try to inject its instructions at the same time". I have not reproduced it deliberately, but the mechanism below fully explains it and every step is read from the code or measured on the live daemon.
What happens
AgentControl.sendis the only delivery call, and its own javadoc says what it does:So a nudge arriving while the operator is mid-sentence does not merely interleave. It pastes into a prompt box that already holds a partial line and then presses Enter. The operator's unfinished message is submitted, with the nudge text attached. That is the harm — not the stray text, the forced submit.
Why the existing gate does not stop it
Every nudge path gates on the same predicate,
herdr/AgentStatus.java:36:That status describes the agent. An idle agent with the operator halfway through typing reports
idle— byte-identical to an idle agent with an empty prompt box. There is no signal anywhere in this chain for "the human has uncommitted keystrokes", so the gate is blind to exactly the state it needs to see.Three loops share the gate and all inject into a lead's own pane:
msg/ReplyPushLoop.java:401status.injectable()msg/LeadHeartbeatLoop.java:241status.injectable()msg/LeadCoordLoop.java:147status.injectable()Measured on the live daemon
ReplyPushLoopis the one hitting the operator. Counted in the currentfleetd/fleetd.out:leadHeartbeatis configured live but withquietNudgeCap: 0, so it never sends a content-free nudge and is not the main source.Note
ticket 1/5: a nudge repeats up to 5 times per ticket until it is collected. So an uncollected ticket multiplies the collision chances, and a lead running several async delegations is nudged often.The signal already exists
inject/StatusRefiner.java:31-35already reads the right region:And
classifyalready inspects the input box —pane.contains("│ >"). It treats the box's presence asIDLEand does not distinguish an empty box from one holding the operator's draft. A Claude Code prompt mid-typing looks like│ > fix the issue when I typ…; empty it is│ >and nothing more.But
refinereturns early on any non-UNKNOWNstatus (:67,if (raw != AgentStatus.UNKNOWN) return raw;). A healthy lead pane reportsidle, so the refiner never runs for it. The fix cannot live inclassifyalone.Proposed
Add a draft check to the lead-nudge gates only. Before injecting into a lead, read the
detectionregion and treat a non-empty input box as not-injectable. The three loops already handle "not injectable → hold and retry", andReplyPushLoop.injectNudge:754-761deliberately re-reads its pending work on every attempt, so nothing is lost by waiting — the nudge simply lands after the operator presses Enter.Scope it to leads. A spawned member has no human typing into it, and a pane read per member poll would add herdr traffic for a state that cannot occur.
Two things to decide, not assume:
ReplyPushLoophas a 5-reminder cap and backoff, so this degrades to "delayed", not "lost" — but it should be bounded and logged rather than silent.agent.promptpastes and submits atomically, losing that race still submits the operator's line. Pasting without Enter would make the failure harmless, but it would also stop a nudge waking a genuinely idle lead, which isReplyPushLoop's whole purpose. So the conditional is the better shape: empty box ⇒ paste and submit as now; draft present ⇒ do not inject at all. That keeps the wake behaviour and removes all but the race.I am not proposing a knob. The current behaviour is wrong for every operator, not a preference.
Correction — PR #765 must not merge as it stands. The box marker is wrong.
This is the thing the PR itself asked me to re-measure ("the one thing I would most want re-measured against a live lead pane before merge"). I measured it. The gate can never clear on this host, so every lead nudge would be held forever.
The design, the seam choice and the fail-closed direction are all right. Only the marker string is wrong. Fixing it is small.
What I measured (2026-10-05, read-only,
herdr agent read --source detection)Five live Claude Code panes, counting both markers in each pane's detection region:
│ >❯wB:p1(anki, idle)w9:p1(vms, idle)w2:p1D(lead: opus, working)wA:p1(trinotes)w1:p17(adev)│ >appears zero times on every pane.PromptBox.BOX_MARKER = "│ >"is the only thing that can produceEMPTY, soEMPTYis unreachable. Every nudge takes theUNREADABLEpath and holds.The live UI draws the input box as a line starting with
❯, between two───rules:That is my own lead pane with an empty box. It is exactly the state the gate must call
EMPTY.Why the tests did not catch it
FakeHerdr.IDLE_PROMPT_BOXdraws an older Claude Code UI:So the fixture and the production code share the same wrong assumption, and they agree with each other. All 10 behavioural tests pass, the before-failure evidence is genuine, and none of it touches the real screen. A test can only disagree with the subject about things the fixture did not copy from it.
Where
│ >came from, and the exact mistakeinject/StatusRefiner.java:100-102:Three alternatives joined by
||.❯is checked first and is the one that fires here. The PR took the second disjunct alone and used it as the sole marker. So this is not a missing case: it is one branch of a working three-way test, lifted out of a context that no longer needed it to work on its own.Two more things the measurement showed
1. The detection region is not a short tail — it is about 76 lines and carries scrollback. The PR's reasoning ("the prompt/footer tail") is not what the region actually is.
ankihas❯on line 36 (an earlier, already-submitted prompt) and on line 75 (the live box). So "take the last marker" is not an optimisation, it is required. Keep that part exactly as it is.2. Match the marker at the start of the line. In my own pane the detection region contained
❯twice inside quoted text at columns 8 and 9 — my own measurement command, echoed on my screen. The live box was at column 0. So the rule should be the last line whose first character is❯, not the last occurrence anywhere. Otherwise a pane that prints the glyph in ordinary output will be read as a draft.What to change
BOX_MARKERwith the live marker, and keep│ >as an accepted alternative rather than deleting it — the same reasonStatusRefinerkeeps all three. A pane may be an older client. The empty test becomes: the last line starting with either marker has nothing after it but whitespace or a cursor glyph.❯as "a shell prompt where the box should be, therefore unreadable". That line is the box, and treating it as unreadable is what breaks this.IDLE_PROMPT_BOX— do not edit the existing one, 5 tests inStatusPollerRoutingTest,ClaudeCodeLauncherTestandSessionManagerTestdepend on its current meaning, as the PR found. Use the live capture above for the empty case and the draft case.herdr, the three call sites,WAIT_BUSY/DRAFT_HELD, the hold-on-unreadable direction, the 20-hold warning that logs a count and never the text, and not wiring it intoinject/Injector.And the bug is real and common — two panes were holding a draft right now
While measuring I found unsubmitted text sitting in two idle panes:
anki:yes, send it to lead: opusvms:draft the observer row for CLAUDE.mdBoth agents report
idle, soinjectable()is true for both, and a nudge to either would have submitted the operator's half-finished line. That is the reported bug, caught in the wild, twice, in one sample of five panes. It also means theUNREADABLE-holds-everything behaviour would have hidden the real fix behind a feature that silently stopped working.On the honesty of the report
The PR flagged this risk itself, said plainly it could not measure it from a worker, and named it as the one thing to check before merge. That is why it took one command to find instead of a week of missing nudges. The gate was right to be built fail-closed, and right to be reported as unverified.
Merged to
mainas7dad054and live since 11:19:29 today.What I checked myself.
MVN_EXIT=0,BUILD SUCCESS, 179 surefire XML files, 2167 tests, 0 failures. The redeploy build reported the same 2167.+27 @Test, -0against the worker's merge-base02eff4c.PromptBox.classifyagainst real live pane captures: my own empty lead pane gaveEMPTYchars=0, and three panes holding unsent text gaveDRAFTwith 32, 26 and 22 characters.scripts/redeploy-fleetd.sh --yes. Old pid 9796 exited, new pid 96638, jarb0b40b6ccd4b->1924721cdb40, freshfleetd listeningline at 11:19:29, no ERROR lines since restart,fleet_whoamistillprimary.lsof -p 96638shows the live process holdsfleetd/run/fleetd.jar, andjavapon that jar'sPromptBoxprints both❯and│ >. So the running daemon has the gate.Marker census, re-measured today on 7 live panes (my own excluded, because my pane echoes my own command text):
❯at line start│ >esc to interruptThis is why the first version of the gate could never clear: it carried only
│ >, lifted out ofStatusRefiner's working three-way||. All 10 unit tests still passed, because the fixture drew the same older UI the check looked for. A fixture copied from the production code cannot disagree with it about the real world.The 1-3 hits per pane also confirm the "take the last box line" rule is needed, not a nicety: the
detectionregion is about 73-76 lines and carries scrollback, including prompts already submitted.Instruction surface updated, as the project rule requires for an injector/status-gating change:
CLAUDE.mdinvariant 4 now says a lead's own pane has a second gate -ac436ef.wiki/11-Features.mdhas an entry: what it does, no knob (always on), whyinjectable()could not see this, and five gotchas - wiki0df2d9b.wiki/7-Use-Cases.mdregenerated; the sync check printsin sync: True. Both pushed and verified by ref.Two things I am NOT claiming. The
esc to interruptcensus above proves nothing, because none of those 7 panes was mid-turn. The separate evidence is that a lead pane that was generating showed✢ Whirlpooling… (29s · ↓ 1.5k tokens)with that string absent, so the marker looks inert. A follow-up ticket covers it, together withStatusRefiner.java:100, which testspane.contains("❯")with no start-of-line rule.