fleetd #801: name the inbox route in timeout receipts; stop dropping a late completion #807
Closed
agent
wants to merge 0 commits from
worker/801-blocking-send-receipt-abe6ca-14 into main
pull from: worker/801-blocking-send-receipt-abe6ca-14
merge into: fleet:main
fleet:main
fleet:worker/808-0c71eb-2
fleet:worker/797-disable-box-gate-5b5478-12
fleet:worker/799-a76336-9
fleet:worker/796-a7911a-5
fleet:worker/778-e301b0-1
fleet:worker/791-fleet-plugin-0-3-0-9e25b0-2
fleet:worker/790-observer-to-lead-send-523d45-1
fleet:worker/788-eb7dd0-1
fleet:worker/782-styling-probe-4ceb10-9
fleet:worker/778-12988a-4
fleet:worker/782-18f8bc-5
fleet:worker/780-faf58b-3
fleet:worker/759-authz-comment-and-role-list-e3f0e5-5
fleet:worker/756-758-observer-pane-discovery-7e6ffd-1
fleet:worker/759-role-model-comments-5d8409-3
fleet:worker/743-pane-discovery-ad5b75-5
fleet:worker/743-observer-send-4706db-6
fleet:worker/749-edge-baseline-28d1a0-3
fleet:worker/748-dead-comment-refs-f42ac5-4
fleet:worker/737-9c61d3-4
fleet:worker/737-a263f3-3
fleet:worker/737-20d1d9-1
fleet:worker/737-b038f7-2
fleet:worker/726-unit2-75cb13-4
fleet:worker/737-owner-key-ff061f-10
fleet:worker/736-presence-forget-f35144-9
fleet:worker/705-observer-14c258-6
fleet:worker/722-024c34-5
fleet:worker/726-ea34a0-2
fleet:worker/726-10cbf0-1
fleet:worker/729-5961c6-3
fleet:worker/727-ee14ed-3
fleet:worker/719-bdd95e-4
fleet:worker/702-4f5c7f-2
fleet:worker/715-5c43fc-1
fleet:worker/721-70f9ea-5
fleet:worker/718-99362b-2
fleet:worker/task-15-af0d10-12
fleet:worker/task-16-50a702-13
fleet:worker/task-12-4d0479-9
fleet:worker/task-13-823ce2-10
fleet:worker/705-ticket-owner-af9928-8
fleet:worker/703-list-collaborators-9c06c2-7
fleet:worker/669-example-truth-0b303d-6
fleet:worker/669-collab-deliverability-9ba859-3
fleet:worker/669-collab-reload-report-2a21bd-4
fleet:worker/669-7e80a6-1
fleet:worker/669-unit-d-efbbd7-1
fleet:worker/669-1b786a-1
fleet:worker/669-1d1d9f-1
fleet:worker/692-4afb9d-2
fleet:worker/689-02fced-13
fleet:worker/693-cf23fa-14
fleet:worker/677-fix-lead-collision-f69073-12
fleet:worker/638-fix-overmask-dbb1bf-11
fleet:worker/675-5b7478-4
fleet:worker/669-unit-a-70cc8f-3
fleet:worker/677-8cdaaf-5
fleet:worker/638-a7b391-1
fleet:worker/683-4536d6-2
fleet:worker/651-a75bbe-8
fleet:worker/680-20607d-7
fleet:worker/664-c12e95-3
fleet:worker/668-08534d-4
fleet:worker/672-0f2469-2
fleet:worker/670-7d1022-1
fleet:worker/661-ac7c28-2
fleet:worker/664-37fb9b-3
fleet:worker/663-remove-3arg-read-3f6783-1
fleet:worker/659-remove-dead-backcompat-ba5e6f-1
fleet:worker/637-revision-60a488-23
fleet:worker/656-redact-regression-tests-892903-19
fleet:worker/637-context-gauge-threshold-466eb5-16
fleet:worker/639-redact-line-numbers-de4ac4-17
fleet:worker/641-set-reformat-guard-6f96a4-18
fleet:worker/642-herdr-guard-scope-5de0e4-15
fleet:worker/650-javadoc-scope-95f3b3-14
fleet:worker/612-01e9f7-13
fleet:worker/612-a-r4-quarantine-outage-7ab0e8-5
fleet:worker/612-a-r9-r11-capacity-coverage-peers-cfcc79-7
fleet:worker/612-a-r10-loophealth-ccc872-8
fleet:worker/612-a-r12-turnregistrar-9e3bb7-9
fleet:worker/612-a-r5-leadconfigdir-9e70cf-6
fleet:lead/config-edit-redact-anchor-wording
fleet:worker/config-edit-seam-ca8dc1-1
fleet:worker/612-r67-630-lifecycle-290b8d-3
fleet:worker/629-625-ports-seams-da7d5d-4
fleet:worker/612-r12-exhaustion-f37cd7-1
fleet:worker/612-r38-amqp-24b083-2
fleet:worker/fleetd-612-unita-87807e-1
fleet:worker/612-b3-mcpwirings-da2b58-3
fleet:worker/612-b2-cb185-176d3a-2
fleet:worker/612-b1-completion-457459-1
fleet:worker/612-agaps-73a926-2
fleet:worker/608-sleeps-3a64ff-3
fleet:worker/621-b4520b-1
fleet:worker/618-b83894-2
fleet:worker/fleetd-615-e05481-5
fleet:worker/lead-autocompact-5f1ab2-3
fleet:worker/fleetd-613-f85deb-3
fleet:worker/fleetd-608-flaky-nudge-test-d0c2d1-3
fleet:worker/lead-context-gauge-ad404f-1
fleet:worker/gauge-wiring-9158c1-4
fleet:worker/redeploy-slowstart-ead0e5-5
fleet:worker/charter-bytes-13668c-6
fleet:worker/rollover-outcome-291483-2
fleet:worker/589-f64303-2
fleet:worker/593-1a8025-5
fleet:worker/589-fcd2aa-1
fleet:worker/568-9fdaa2-3
fleet:worker/571-attempted-outcome-5739f7-2
fleet:worker/581-completionresolver-cas-sites-0542b7-6
fleet:worker/562-loop-health-wiring-test-99611c-5
fleet:worker/562-surface-loop-health-7df5cc-4
fleet:worker/575-waiter-cleanup-sites-62ad80-1
fleet:worker/572-answer-lock-release-46a9ae-5
fleet:worker/567-probe-channel-leak-a38fc5-6
fleet:worker/551-record-before-send-7cbf56-1
fleet:worker/561-listener-fanout-survives-a-throw-61d538-2
fleet:worker/555-redeploy-main-flow-seam-65c2f5-2
fleet:worker/556-injector-owns-registration-e027a5-1
fleet:worker/552-post-restart-mktemp-abort-bc2672-4
fleet:worker/553-onstatus-completion-leak-0da881-2
fleet:worker/550-shasum-linux-196132-1
fleet:worker/538-loop-dies-on-error-4a5eeb-6
fleet:worker/426-health-coverage-ef1fd4-4
fleet:worker/504-failed-reported-clean-3cfd66-3
fleet:worker/537-capturedlog-close-e4c437-2
fleet:worker/459-broken-link-targets-cadc17-5
fleet:worker/535-appender-leak-fe74c1-1
fleet:worker/512-part2-shutdown-detection-434701-9
fleet:worker/529-logger-level-sweep-2a5533-8
fleet:worker/528-drain-gate-call-site-5de83d-7
fleet:charter/forge-mcp-vs-token
fleet:worker/521-swap-guard-unpinned-28e931-5
fleet:worker/519-probe-test-harness-d25ab8-4
fleet:worker/525-logger-level-leak-1b4eb0-6
fleet:worker/518-fleetmcp-resolver-wiring-8ef96c-1
fleet:worker/512-drain-complete-line-7edd71-3
fleet:worker/517-abort-branch-and-jar-id-41b641-2
fleet:worker/500-9e52c9-3
fleet:worker/509-4912f4-2
fleet:worker/511-9a4b23-1
fleet:worker/493-479f45-2
fleet:worker/505-03f8b2-1
fleet:worker/492-followup-detect-unclear
fleet:worker/501-a31fa0-7
fleet:worker/498-451d1c-5
fleet:worker/494-1015ce-2
fleet:worker/492-209647-1
fleet:worker/489-001902-2
fleet:worker/480-relative-handover-path-906323-1
fleet:worker/480-b-handover-skill-45bf1f-5
fleet:worker/474-followup-source-pin-f54a55-17
fleet:worker/474-charter-check-on-reload-f54a55-17
fleet:worker/466-quarantine-repeatcount-report
fleet:worker/393-opencode-skill-seeding-71854b-13
fleet:worker/469-canonical-tool-names-2a472a-16
fleet:worker/466-quarantine-escalation-5ae9c1-15
fleet:worker/446-hot-exhausted-pattern-0af580-6
fleet:worker/464-charter-tool-name-guard-a85635-12
fleet:worker/463-listfleet-default-fails-open-f1c76c-11
fleet:worker/458-invariant-5-by-purpose-862f9a-10
fleet:worker/439-coordinator-row-gate-bc032a-8
fleet:worker/449-herdr-protocol-576015-4
fleet:worker/450-abstract-spawn-599e1c-5
fleet:worker/437-ack-refuses-177d91-1
fleet:worker/444-placement-window-feb56a-2
fleet:worker/440-helddurable-derived-d462d7-13
fleet:worker/425-rework-placement-resolve-c58ba1-9
fleet:worker/421-lead-peek-held-msgs-cdbad2-10
fleet:worker/435-fixed-policy-cap-fe11de-12
fleet:worker/422-gate-state-observability-9e79d6-11
fleet:worker/431-memberregistry-live-readers-cdbad2-10
fleet:worker/424-architect-slot-hot-038b41-7
fleet:worker/422-model-gate-spawn-c29f48-6
fleet:worker/425-default-profile-live-f55534-8
fleet:worker/415-coverage-wording-2cbf9c-5
fleet:worker/416-3ad1da-1
fleet:worker/418-588283-3
fleet:worker/deterministic-stamp-race-409-3cb7b6-10
fleet:worker/armed-reads-live-config-404-ed931f-9
fleet:worker/reply-peer-refusal-391-5a34bd-7
fleet:worker/models-allowlist-aa9e9b-3
fleet:worker/ttl-stamp-race-399-f1122f-8
fleet:worker/scrub-receipt-400-316b3e-5
fleet:worker/exhaustion-detection-395-105105-6
fleet:worker/scrub-abort-394-316b3e-5
fleet:fix/scrub-uid-abort
fleet:worker/task-scrub-517574-2
fleet:worker/t386-clock-bd5b78-4
fleet:worker/t384-scrub-813790-5
fleet:worker/t381-cc-748314-2
fleet:worker/t373-336973-2
fleet:worker/t365-3920c5-3
fleet:worker/t358-6e989b-1
fleet:worker/t355-8b321c-1
fleet:worker/fleetd-369-hermetic-git-tests-e8b19a-3
fleet:worker/fleetd-368-stale-lead-binding-f5682e-2
fleet:worker/fleetd-360-deploy-units-0d3793-1
fleet:worker/359-dead-lead-tabs-f1253b-4
fleet:worker/362-worktree-skills-c03e51-3
fleet:worker/361-coord-visibility-655144-1
fleet:362-plugin-visibility-and-drift
fleet:worker/errscan-bed2ca-2
fleet:worker/amqp-log-identity-bed2ca-2
fleet:worker/withdefaults-guard-561704
fleet:worker/sleepguard-82076d-1
fleet:worker/fd334-9ee1b6-5
fleet:worker/fd348-f1ab27-4
fleet:worker/fd335-a71c35-1
fleet:worker/fd342-174a17-2
fleet:worker/fd345-490d0f-3
fleet:worker/fleetd-337-5ec7d4-21
fleet:worker/fleetd-341-af5a6b-24
fleet:worker/fleetd-339-5ca0a2-23
fleet:worker/fleetd-338-83a4a1-22
fleet:worker/fleetd-333-281f46-18
fleet:worker/fleetd-329-11bdbb-16
fleet:worker/fleetd-330-2770fb-17
fleet:worker/fix-326-50506e-15
fleet:worker/fix-324-3e9bbf-14
fleet:worker/fix-323-b8287d-13
fleet:worker/fix-316b-bd0860-11
fleet:worker/fix-318-76ca36-9
fleet:worker/fix-317-486aec-8
fleet:worker/fix-315-ce47c5-6
fleet:worker/fix-307-275890-6
fleet:worker/fix-308-b4f664-7
fleet:worker/fix-309-ec3939-8
fleet:worker/fix-310-7a3974-9
fleet:worker/fix-302-52ad0e-9
fleet:worker/fix-298-ce1acb-8
fleet:worker/fix-297-66bd11-7
fleet:worker/fix-296-104622-6
fleet:worker/fix-293-bare-closetab-eb22b5-3
fleet:worker/fix-280-gone-ask-lapse-bca98e-2
fleet:worker/fix-290-reapidle-guard-coverage-9b0dd1-1
fleet:worker/fix-285-trust-seed-8f3565-10
fleet:worker/fix-284-backend-error-seat-85912c-11
fleet:worker/fix-282-chained-ask-e6d0bb-8
fleet:worker/fix-283-teardown-leaks-f40dfa-9
fleet:worker/fix-281-pin-handler-actions-4921ac-7
fleet:worker/audit-rendezvous-lifecycle-d072ae-2
fleet:worker/audit-health-placement-1a2476-6
fleet:worker/audit-teardown-exits-e207a5-3
fleet:worker/audit-launcher-asymmetry-27e370-4
fleet:worker/audit-rest-authz-6ca53c-5
fleet:worker/investigate-275-abandon-asking-fdef52-8
fleet:worker/fix-274-worktree-leak-b0095d-7
fleet:worker/fix-273-exhausted-pattern-9665b5-6
fleet:worker/fleetd-267-model-check-bd8068-1
fleet:worker/fleetd-131-archunit-18b834-7
fleet:worker/fleetd-266-sshagent-rename-a014ff-6
fleet:worker/fleetd-184-uid-claim-8e1f31-4
fleet:worker/fleetd-184-warn-b381ee-10
fleet:worker/fleetd-184-docs-be1d12-9
fleet:worker/fleetd-257-9bf010-7
fleet:worker/fleetd-103-23a113-6
fleet:worker/fleetd-247-342356-5
fleet:worker/fleetd-116-04dea8-4
fleet:worker/fleetd-252-a830e0-3
fleet:worker/fleetd-111-7e8673-9
fleet:worker/fleetd-155c-f8ef4b-8
fleet:worker/fleetd-176-b928ca-3
fleet:worker/fleetd-249-7a7878-2
fleet:worker/cb248-composition-root-b-9acdf7-15
fleet:worker/cb148-envrc-default-fa6c82-12
fleet:worker/cb201-unit5-wiring-6c12e6-8
fleet:worker/cb241-fallback-echo-1175e9-11
fleet:worker/cb149-trust-dialog-2392a5-9
fleet:worker/cb134-148-overlay-visible-c9b986-10
fleet:worker/cb234-session-id-keyed-04e1fc-1
fleet:worker/cb201-unit3-nudge-abdf5c-6
fleet:worker/cb201-unit2-policy-c1102c-5
fleet:worker/cb201-unit4-outcome-a13bfa-7
fleet:worker/cb201-unit1-classifier-91b9b1-4
fleet:worker/cb201-227-refine-831980-3
fleet:worker/cb175-model-readback-0f085f-1
fleet:worker/cb222-charter-tmpdir-17f013-1
fleet:worker/cb226-architect-slot-race-cd3aa8-3
fleet:worker/cb224-worktree-root-group-024523-2
fleet:worker/cb-123-role-demotion-c600f7-2
fleet:worker/cb-219-opencode-roots-1f677e-1
fleet:worker/cb214-claude-session-id-b9eab4-4
fleet:worker/cb213-zdotdir-wrong-process-dd6de4-3
fleet:worker/cb211-exhaustion-classification-9546e0-2
fleet:worker/cb137-ambiguous-task-4df3d8-4
fleet:worker/cb209-agentsessionid-4dfdb6-2
fleet:worker/cb185-hostenvnames-2692b5-3
fleet:worker/cb206-opencode-sqlite-128718-2
fleet:worker/cb185-worktree-group-fc0c99-1
fleet:worker/cb-137-ask-ticket-e7760c-2
fleet:worker/cb-172-broker-uri-d36ae4-4
fleet:worker/cb-175-model-readback-76ead6-3
fleet:worker/cb-161-pane-ancestry-293510-1
fleet:worker/cb-164-rebase-885863-8
fleet:worker/cb-164-empty-scrape-false-success-1a80af-3
fleet:fix/cb-197-ticket-ttl-from-completion
fleet:worker/cb-189-remote-url-coverage-4692f3-1
fleet:worker/cb-185-blockers-027756-4
fleet:worker/cb-192-gap-log-11b631-2
fleet:worker/cb-633-fix-5f4396-3
fleet:worker/cb185-router-d6436d-3
fleet:worker/cb185-router-routing-gaps-9e9d33-3
fleet:worker/cb185-paneids-992586-2
fleet:worker/cb-633-allow-list-union-ed374b-1
fleet:worker/cb-157-credential-in-remote-url-496e44-2
fleet:worker/cb-641-health-herdr-evidence-8f1f54-6
fleet:worker/cb-640-health-msg-evidence-99c9cd-1
fleet:worker/cb-642-fleets-status-skill-bbbc40-5
fleet:cb-634-ide-mcp
fleet:worker/lead-comms-wiring-c014b9-7
fleet:worker/lead-mailbox-c19577-6
fleet:worker/autocompact-window-82bc2f-5
fleet:worker/cb-634-probe-18056f-4
fleet:worker/cb635-broker-urienv
fleet:worker/cb-632-config-retry-8e0efa-7
fleet:lead/cb-622e-claude-md
fleet:lead/cb-622-followup
fleet:worker/cb-622a-165dff-1
fleet:lead/cb-622d-opencode-mount
fleet:worker/cb-622b-717c67-2
fleet:worker/cb-622c-ab7759-3
fleet:worker/cb-617b2-20ca4b-3
fleet:worker/cb-617a-5c2f4a-1
fleet:worker/cb596-4e49ef-3
fleet:worker/cb586-10500c-1
fleet:worker/cb-606-b9343a-25
fleet:worker/cb604-1445f8-24
fleet:worker/cb582-477374-21
fleet:worker/cb584-8c2281-22
fleet:worker/cb600-e6b9a9-20
fleet:worker/cb602-ce257f-19
fleet:worker/cb601-b42837-18
fleet:worker/cb598-6c7ba7-17
fleet:worker/cb599-740fe4-16
fleet:worker/cb597-282224-15
fleet:worker/cb590fix-185e9a-10
fleet:worker/cb528-recovery-race
fleet:worker/cb594-96bead-8
fleet:worker/cb590-916766-2
fleet:worker/cb527-997d99-3
fleet:worker/cb592-env-leak-3cbf9c-1
fleet:worker/cb588-async-ticket-nudge-3218f7-5
fleet:worker/cb578b-9dcb13-6
fleet:worker/cb581-d24826-5
fleet:worker/m2-u5-ef8c42-15
fleet:worker/cb578a-516499-2
fleet:worker/cb576-01a04b-17
fleet:worker/cb579-lead-tab-acba06-20
fleet:worker/cb580-terminal-health-ed6058-21
fleet:worker/cb577-f36fdc-18
fleet:worker/cb573b-3db06f-16
fleet:worker/cb568c-f36fdc-18
fleet:worker/cb568-drop-cause-c3ac1c
fleet:worker/cb575-cancelled-notification-c3ac1c
fleet:worker/m4-sol-a2cbec-3
fleet:worker/cb574-async-ask-c3ac1c
fleet:worker/cb573-health-model-8ca857-14
fleet:worker/cb572-unknown-target-7f2e35-13
fleet:worker/u4-700706-9
fleet:worker/u3-b9fcb6-6
fleet:worker/u2-ef5b68-4
fleet:worker/u1-469dce-1-clean
fleet:worker/u1-469dce-1
fleet:worker/cb-564-health-events-70cf7e-2
fleet:worker/cb-565-recycle-drops-role-98e58f-3
fleet:worker/cb-563-missing-reply-df2866-1
fleet:worker/cb-562-readiness-gate-silent-6c23c9-3
fleet:worker/cb-560-architect-presence-da8155-1
fleet:worker/cb-561-architect-silent-off-a71cab-2
fleet:worker/cb-548-bind-architect-slot-fe1b8c-1
fleet:worker/parity-overlay-settings-5fb711-1
fleet:secrets-central-store
fleet:cb-559-hot-key-correction
fleet:cb-557-fleet-role-pools
fleet:worker/cb-553-maxload-explicit-spawn-305ee3-6
fleet:worker/cb-551-idle-lead-heartbeat-f1633c-1
fleet:worker/cb-544-drain-preserves-worktree-925fad-3
fleet:worker/cb-552-docs-sync-1cb9cf-4
fleet:worker/cb-548-rendezvous-guard-rebased
fleet:worker/cb-548-rendezvous-guard-116b53-10
fleet:worker/cb-548-authz-v2-586df6-8
fleet:worker/cb-548-authz-264363-5
fleet:salvage/cb-528b-codex-home
fleet:salvage/cb-528a-codex-launcher
fleet:CB-518-primary-flow
fleet:feature/peer-launcher-spi
fleet:cb-103-injector
No Reviewers
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#807
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 "worker/801-blocking-send-receipt-abe6ca-14"
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?
fleetd #801: a blocking
fleet_send/fleet_askthat times out gave the caller a receipt it could not act on, and an unstructured completion that arrived late was silently lost.Issue #801's own correction comment (2026-10-07T05:13:31+02:00) supersedes its "Measured today" section — a blocking send creates no ticket, ever (
new Task(...)appears exactly once inMessageService.java, in thesendAsyncpath only). This PR follows that correction, not the original report.Part 1 — receipt wording (MCP and REST)
FleetMcp.formatReply's TIMED_OUT_WORKING / TIMED_OUT_QUEUED / BUSY case, andFleetApp.writeReply's equivalent default-casedetail, now:fleet_poll{target="<sessionId>"}(MCP) orGET /sessions/{id}/replies(REST) to drain the target's inbox — the same route a latefleet_replywith no open send already uses (MessageService.javaline 615'sinbox.publishcall)The fact itself is one constant,
MessageService.NO_TICKET_NO_RESEND, shared by both surfaces rather than duplicated.Part 2 — found: the waiter survives the timeout (the asymmetry is real)
Investigated per the brief, not asked about:
MessageService.send()'scatch (TimeoutException e)block (around line 1033) callsinjector.cancel(delivery)— that cancels the Injector's pending delivery, not the rendezvous future — and then, infinally,rendezvous.close(target, reply), which only deregisters the waiter from the session-keyed map used byresolve()/resolveQuestion(). It never completes or cancels theCompletableFuture<Rendezvous.Resolution>itself.Separately,
Rendezvous.resolveCompletion/resolveFailure/resolveExhausted(the CB-106 turn-completion fallback, driven byCompletionResolver) act on a captured future reference, not a session lookup — by design (CB-116 waiter identity), so a late completion can't land on the wrong, later turn. So the future stays open and completable after a TIMED_OUT_WORKING/TIMED_OUT_QUEUED return, and the fallback can and does resolve it later with nobody left listening — this matches the ticket's own measured log evidence (timed out (delivered=true)followed ~16s later by a successful turn-completion-fallback resolution).Fix: added
MessageService.strandLateResolution(target, waiter), attached viawhenCompleteright beforesend()returns its TIMED_OUT_* reply. When that captured future later resolves withRendezvous.Kind.COMPLETION(and only that kind — an explicitfleet_reply/REPLY still reaches a live waiter throughreply()'s own session lookup, so routing REPLY here too would risk a racing double-publish), it publishes totarget's inbox via the exact same mechanismreply()already uses for a stranded structured reply (inbox.publish+strandedReplies.put+pushLoop.onReplyQueued) — no second mechanism invented.answer()'s own timeout path is unaffected: it never re-enters the injector, so no completion fallback can arm it (per its pre-existing in-code comment).Tests
FleetMcpTest: three new tests (TIMED_OUT_QUEUED,TIMED_OUT_WORKING,BUSY) each assert the text containsfleet_poll{target="..."}and does not contain "retry".FleetAppTest: extended the two existing REST timeout tests with a shared assertion helper checkingdetailnames/sessions/{id}/repliesand omits "retry". (No pre-existing BUSY-outcome REST test existed to extend; not fabricated.)MessageServiceTest: new testaCompletionThatArrivesAfterTheCallerGaveUpLandsInTheInboxdrives a real timeout then a real turn-completion fallback resolution viaAgentStatustransitions, and asserts the content lands in the target's inbox, drainable viadrainReplies. The pre-existingcompletionFallbackResolvesATurnThatNeverCalledFleetReplytest gained one assertion that a completion resolved by a live caller does not also land in the inbox (no duplicate publish).Build
mvn -o clean installinfleetd/, run unpiped:BUILD SUCCESS,Tests run: 2231, Failures: 0, Errors: 0, Skipped: 0.Out of scope, not touched
CLAUDE.md/wiki//docs/, daemon redeploy,.mcp.json/opencode.json/.autoenv, timeout values.The TIMED_OUT_WORKING/TIMED_OUT_QUEUED/BUSY receipt (MCP and REST) named no recovery route and invited a bare retry, which can duplicate the delivery. It now names fleet_poll{target=...} (or GET /sessions/{id}/replies) and states that a blocking send is never tracked by a ticket. A blocking send's TimeoutException path leaves its rendezvous waiter in place: Rendezvous.close only deregisters it from future session lookups, while the turn-completion fallback resolves it through a captured future reference and can still succeed after the caller gave up. With nobody left listening, that late completion is now published to the target's inbox, the same route an unstructured fleet_reply already uses when it arrives with no caller waiting.Lead review. Part 2 is right and I want it. Two defects to fix first, delegated as
task-7da785-104on this same branch.Build, verified by me on the merge result.
f2e025cmerged ontomain(a49d4dc, which already carries #778, #797, #803 and #804) in a throwaway worktree. Clean auto-merge acrossFleetMcp,MessageService,FleetAppandMessageServiceTest.XML tally as a second instrument:
files: 183 tests: 2240 failures: 0 errors: 0 skipped: 0.Part 2 is the right finding and the right shape. The waiter does survive the timeout:
send()'scatch (TimeoutException)cancels the injector delivery andfinallyrunsrendezvous.close, which only deregisters from the session map, whileRendezvous.resolveCompletioncompletes a captured future reference. Routing that lateCOMPLETIONto the target's inbox reusesreply()'s existing path rather than adding a second mechanism, and confining it toKind.COMPLETIONavoids racingreply()'s own publish. That is the asymmetry I described in my correction comment on #801, fixed where it actually lives.Defect 1 — the receipt names an action most callers are refused
The new wording ends with
Poll fleet_poll{target="<id>"} to drain its inbox for a late reply.fleet_pollwith atargetisDRAIN:pollActionreturnsisBlank(target) ? TASK_READ : DRAIN. AndAuthz.java:175iscase SPAWN, STOP, DRAIN, HANDOVER -> caller.isPrimary();.SEND, meanwhile, is granted to a primary, an architect, a collaborator and an observer. So three of the four roles that can reach this receipt are refused the only action it tells them to take, and a blocking send is the default, so any observer pane that sends and times out gets it.It is worse than unusable advice. The inbox the text is published to is drainable only through that same primary-only
DRAIN, so for a non-primary caller the late completion is now published somewhere they can never read. The receipt must not promise a recovery the caller cannot perform.This is the rule #778 established for nudges, arriving in a user-facing receipt instead. Unlike the
mayTaskReadNudgecase I rejected on #806, this condition genuinely varies by role, so the realAuthz.permitscall at the point where thePrincipalis still in scope is the right fix here.Defect 2 — the only
inbox.publishwhose failure is invisiblestrandLateResolutionpublishes inside awhenCompletecallback with no try/catch, and discards the dependent stage.AmqpReplyInbox.publishthrowsIllegalStateExceptionon an unroutable, unconfirmed or interrupted publish. A throw there is swallowed: the reply is lost,strandedRepliesis never set so fleet health cannot see it, and nothing is logged.The two existing sites both behave better.
reply()'s publish is unguarded but runs on the worker's ownfleet_replythread, so a throw reaches that caller.abandon()'s put-back atMessageService.java:873is wrapped, and its comment names this hazard as fleetd #335. The new site is neither visible nor guarded.Credit where it is due
The worker's own caveats named the REST
BUSYtest gap and theanswer()timeout path explicitly rather than papering over them. Theanswer()note is what made me check that path's role grant, which is half of defect 1.A TIMED_OUT/BUSY receipt named fleet_poll{target}/GET /sessions/{id}/replies unconditionally, but that route needs Authz.Action.DRAIN, which is primary-only, while SEND and ANSWER are also granted to an architect, a collaborator, and an observer. Those callers were told to run a call they are refused, and the late reply they were pointed at is published somewhere they can never read. Thread each caller's own DRAIN grant into formatReply (MCP) and writeReply (REST); name the route only when the grant holds, otherwise say the reply cannot be recovered on that channel. Also wrap strandLateResolution's whenComplete callback in try/catch: an exception thrown there lands in the discarded dependent stage and is never rethrown, so an unguarded inbox.publish failure would lose a late reply with no log line at all.They had no production caller — only test call sites — and the default pointed the unsafe way: true means naming fleet_poll{target}, which is the exact receipt this ticket is fixing. Keep one signature each and pass mayDrainPoll explicitly at every test call site instead.Merged locally and pushed to
mainas11352d0. Closing this PR — the branch content is onmain, so there is nothing left for the forge to merge.Verified on the merge result, not on the branch:
mvn clean installin a throwaway worktree, no[ERROR]lines from maven,Tests run: 2244, Failures: 0, Errors: 0, Skipped: 0, tallied from 183 surefire XML files. The branch adds exactly 8@Testmethods. I also confirmed the staged tree matched the tree I built, byte for byte (git write-tree=517590e), so the build I checked is the commit that landed.Two review rounds, both findings fixed:
fleet_poll{target}to every caller. That call isDRAINand primary-only, whileSENDis granted to an architect, a collaborator and an observer as well. Fixed in24782fe, which also extended the same conditional to the RESTdetail— correctly, sinceGET /sessions/{id}/repliesshares theDRAINgate.strandLateResolutionran unguarded inside awhenCompleteaction, where a throw is captured by the discarded dependent stage. Fixed in the same commit, with a test that injects a throwingReplyInboxand asserts both theWARNand thathasStrandedReplystays false.c8e31e1then removed the twosend/answeroverloads that defaulted theDRAINgrant totrue. They had no production caller, and the default pointed the unsafe way.Follow-up shipped with it:
CLAUDE.md(a2cbf50) and the wiki template now say a blocking send creates no ticket, andwiki/11-Features.mdcarries the entry (fleetd.wiki f812689).Pull request closed