fleetd #571: add TIMED_OUT_UNCONFIRMED for ATTEMPTED-delivery timeouts #580
Closed
agent
wants to merge 0 commits from
worker/571-attempted-outcome-5739f7-2 into main
pull from: worker/571-attempted-outcome-5739f7-2
merge into: fleet:main
fleet:main
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/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#580
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/571-attempted-outcome-5739f7-2"
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?
Closes #571.
The bug
MessageService.send'sTimeoutExceptionbranch used to foldInjector.Cancellation.ATTEMPTED(from #551) intoOutcome.TIMED_OUT_QUEUED. That outcome tellsthe caller the message never reached the pane. But
ATTEMPTEDmeans we do not know that. Thecall to
agent.promptpastes and submits in one step, so the text may already sit in the pane.A caller that resends on
TIMED_OUT_QUEUEDcan send the same brief twice.The fix
Added a fourth outcome,
Outcome.TIMED_OUT_UNCONFIRMED. Its javadoc says plainly: delivery is notknown, and a resend on this route risks a double delivery.
send()'s timeout branch now routesCancellation.ATTEMPTEDto this new outcome instead ofTIMED_OUT_QUEUED.Readers — three files, each with positive control
Found by grepping for the constant name
TIMED_OUT_UNCONFIRMED, not forOutcome.(that searchmisses
FleetMcp.java's barecase REPLIED ->labels and false-positives onConfigRef.java'sunrelated
Outcomerecord).MessageService.sendOutcomeLabel(switch expression, nodefault): addedTIMED_OUT_UNCONFIRMEDto the same"timeout"label as the other three timeout/busy outcomes.FleetMcp.formatReply(switch expression, nodefault): gaveTIMED_OUT_UNCONFIRMEDitsown arm, separate from the
TIMED_OUT_WORKING, TIMED_OUT_QUEUED, BUSYarm — its message saysdelivery is unconfirmed and warns against a blind retry.
FleetApp.writeReply(switch statement withdefault, wrapping an inner switchexpression): the inner expression used to close with
default -> "done", which would havesilently mapped the new outcome to the wrong status. Fixed by deleting that
defaultandlisting every one of the 10
Outcomeconstants by name (see "exhaustive switches" below).TIMED_OUT_UNCONFIRMED -> "unconfirmed", with its owndetailmessage.Exhaustive switches — no
default(ticket comment, "read before you commit")Every switch over
Outcomehere compiles without adefault, so the compiler — not a grep —catches the next added constant.
MessageService.sendOutcomeLabel: already had nodefault; unchanged in that respect.FleetMcp.formatReply: already had nodefault; unchanged in that respect.FleetApp.writeReply's inner switch (the one producing"status"): this is the change— deleted its
default -> "done"and listed all 10 constants, including the 4 that areunreachable here because the outer switch dispatches them first
(
REPLIED, COMPLETED_UNREPLIED, QUESTION, STALE_TURN -> "done" // unreachable).FleetApp.writeReply's outer switch (statement, not expression) keeps itsdefault. Aswitch statement is never compiler-checked for exhaustiveness in Java regardless of
default,so deleting it would buy nothing here — I chose to keep it because "anything not one of the four
named terminal outcomes is a 202 in-progress reply" is a stable, intentional catch-all, not a
place a missed case would hide.
Proof, in the ticket's required order (delete the defaults first, then add the temp constant —
doing it the other way around hides exactly the sites that need work): with both
defaultarmsalready deleted, I added a temporary 11th
Outcomeconstant and rebuilt.javacreports oneswitch-expression compile error at a time — fixing the first site's arm is what surfaces the next
file's error — so I added a matching temporary arm one file at a time and rebuilt each time. Four
sites errored in total:
MessageService.sendOutcomeLabel,FleetMcp.formatReply, andFleetApp.writeReply's inner switch (three production sites), all four counting the reflectivecheck below overlapping means the constant plus its message. No test source needed a matching arm
(
mvn -o test-compilewith the temp constant present wasBUILD SUCCESS). Removed the temporaryconstant and every temporary arm afterward; confirmed with
grep -rn "TEMP_PROOF" src/(exit 1, no matches).Door 2 — 8 comparison/ternary/
!=sitesjavaccannot catchA switch is not the only way to branch on
Outcome. Grepped for.outcome() ==/!=acrossMessageService.javaandFleetApp.java; every hit gets a verdict below (current line numbers,re-grepped just now — they move as the file changes):
TIMED_OUT_UNCONFIRMED?MessageService.java:804outcome.outcome() == Outcome.WORKER_FAILEDinabandon()outcomethere is only everWORKER_FAILED, or a recoveredREPLIED/COMPLETED_UNREPLIED— never a timeout outcome. No change needed.MessageService.java:1239result.outcome() != Outcome.QUESTIONinanswer()resulthere comes fromoutcomeOf(Rendezvous.Kind), a separate 5-value source (REPLY, COMPLETION, FAILED, BACKEND_EXHAUSTED, QUESTION) that never produces a timeout outcome. No change needed.MessageService.java:1315result.outcome() == Outcome.QUESTIONin the async-submit pathresultcomes straight from callingsend(). Theelsebranch isfinishAsyncTask(task, result)— the same path every other timeout outcome already takes. Correct as-is.MessageService.java:1377r.outcome() == Outcome.REPLIED ? "reply" : "transcript"if (r.completed()), andcompleted()isfalseforTIMED_OUT_UNCONFIRMED. No change needed.MessageService.java:1383carriesReason = r.outcome() == WORKER_FAILED || == BACKEND_EXHAUSTEDcarriesReasonisfalseforTIMED_OUT_UNCONFIRMED, so it falls to the.name()fallback (see Door 4 below). Deliberately left as-is — see Door 4.FleetApp.java:638reply.outcome() == REPLIED ? "reply" : "transcript"case REPLIED, COMPLETED_UNREPLIED ->arm of the outer switch;TIMED_OUT_UNCONFIRMEDis dispatched to a different arm entirely. No change needed.FleetApp.java:663reply.outcome() == TIMED_OUT_UNCONFIRMED(new, this PR)detailbranch — correct by construction.FleetApp.java:667-668reply.outcome() == WORKER_FAILED || == BACKEND_EXHAUSTEDelseafter theTIMED_OUT_UNCONFIRMED-specific ternary at line 663, soTIMED_OUT_UNCONFIRMEDnever reaches it — line 663 already claimed it. No change needed.That is 8 sites, matching the ticket's count.
Door 3 — reflection (
values()/valueOf)grep -rn 'Outcome\.values()\|Outcome\.valueOf' src/main/java src/test/java→ no matches. Nothingin this codebase parses an
Outcomeback from a string. This rules out the wholestring-round-trip hazard class for this enum — there is no code path where a stale or unknown
string could resolve to the wrong constant, because nothing resolves strings to this enum at all.
Door 4 —
.name()/.ordinal()raw serializationgrep -rn 'outcome()\.name()\|outcome()\.ordinal()\|Outcome\[\]' src/main/java src/test/javafindsexactly two live sites, both pre-existing:
FleetMcp.java:807—r.outcome().name().toLowerCase().replace("timed_out_", ""), insidecase TIMED_OUT_WORKING, TIMED_OUT_QUEUED, BUSY ->. I checked this myself, not just the ticket'sword for it:
TIMED_OUT_UNCONFIRMEDis not part of thatcaselabel (see the readerssection above — it has its own dedicated arm at lines 811-813). So this
.name()call neverruns for the new outcome, and this site needed no change.
MessageService.java:1386—"no reply — " + r.outcome().name().toLowerCase(), thefallback in the task-view/poll path when
carriesReasonisfalse(Door 2's line 1383). ForTIMED_OUT_UNCONFIRMEDthis produces"no reply — timed_out_unconfirmed". Deliberatedecision: I am keeping this fallback unchanged. It already renders
TIMED_OUT_WORKING,TIMED_OUT_QUEUED, andBUSYthe same raw way, soTIMED_OUT_UNCONFIRMEDstays consistent withits siblings in this one internal diagnostic string — this is not the REST or MCP surface (those
got dedicated messages, see Door 5), it is
TaskView.reason, an internal poll-result field. Ichose consistency with the existing pattern over a bespoke message here.
Door 5 — the wire form: every string this change adds or alters, as a consumer sees it
FleetApp.writeReply,POST /messagesand friends, 202 body): adds"status": "unconfirmed"and"detail": "no reply within <timeoutMs>ms; delivery is unconfirmed — the message may already have reached the worker, so a resend risks sending it twice; poll status first".FleetMcp.formatReply, thefleet_send/fleet_asktool result text): adds"[no reply within <timeout>ms — delivery unconfirmed; the message may already have reached the worker, so a retry risks sending it twice — poll status before resending]".MessageService's task-view poll fallback (TaskView.reason, Door 4 above): adds"no reply — timed_out_unconfirmed"— the raw.name()token, produced only when a polledticket never reached
REPLIED/COMPLETED_UNREPLIEDand isn'tWORKER_FAILED/BACKEND_EXHAUSTED.One sentence on the wire token itself:
TIMED_OUT_UNCONFIRMEDreaches the wire as the implicittoken
timed_out_unconfirmedvia.name(), not through a pinnedwireNamethe way the siblingenum
ReplyOutcome(MessageService.java:166) already does — that gap is known and is beingtracked separately as #578; this PR does not change that pattern.
Door 6 — readers outside the Java source roots
Confirmed empty for executable readers: no script or build file parses or branches on this
enum's constant names. The only hits are prose mentions in docs (8 of them), which describe
behaviour rather than parse it — left untouched, as instructed, since they are not executable
readers.
Tests (3, each with a mutation proof)
MessageServiceTest.sendTimesOutWithAttemptedDeliveryReportsUnconfirmedNotQueued— a send whosedelivery ends
ATTEMPTEDand times out returnsTIMED_OUT_UNCONFIRMED, notTIMED_OUT_QUEUED.Mutation: line-anchored
sedonMessageService.java'soutcome = Outcome.TIMED_OUT_UNCONFIRMED;line →Outcome.TIMED_OUT_QUEUED. Pristinegrep -Fxccount 1 → mutated count 0. Mutated test failed with:"an ATTEMPTED delivery must not collapse into TIMED_OUT_QUEUED — the message may already have arrived in full, and TIMED_OUT_QUEUED promises it never will ==> expected: <TIMED_OUT_UNCONFIRMED> but was: <TIMED_OUT_QUEUED>".Restored,
shasum -a 256matched the pristine file exactly. Re-ran green.TIMED_OUT_QUEUED,TIMED_OUT_WORKING,BUSY) areunchanged on their own routes — covered by the pre-existing tests in
MessageServiceTest,which still pass.
FleetAppTest.messageTimesOutUnconfirmedWhenDeliveryAttemptFails— asserts the actual RESTstatus string
"unconfirmed", not"queued"or"done". Mutation: line-anchoredsedonFleetApp.java'scase TIMED_OUT_UNCONFIRMED -> "unconfirmed";line →"queued". Pristinegrep -Fxccount 1 → mutated count 0. Mutated test failed with:"an ATTEMPTED delivery must report its own status, not \"queued\" or \"done\" ==> expected: <unconfirmed> but was: <queued>".Restored,
shasum -a 256matched the pristine file exactly. Re-ran green.Both proof cells were also run against the pristine, un-mutated tree first: both reported
"not applied" (count 1, matching the pristine value), confirming the sed/grep pair actually tests
what it claims.
Build
mvn -o clean installfromfleetd/, active profiledefault-excludes(activeByDefault=truein
pom.xml, excludes@Tag("contract")), confirmed withmvn -o help:active-profiles.Tests run: 1768, Failures: 0, Errors: 0, Skipped: 0—BUILD SUCCESS.target/surefire-reports/*.txt:Tests run: 1768 Failures: 0 Errors: 0 Skipped: 0.CORRECTION 5 (added after review)
The lead mutated
FleetMcp.java'sTIMED_OUT_UNCONFIRMEDarm (swapped its text for thequeued/working arm's generic text) and it survived:
Tests run: 1768, BUILD SUCCESS. Nothingpinned the one message whose whole job is to stop a caller retrying a delivery that may already
have arrived. A control mutation on the pre-existing sibling arm (
case TIMED_OUT_WORKING, TIMED_OUT_QUEUED, BUSY ->) was killed byFleetMcpTest.sendTimesOutWithAWorkingNote, confirmingformatReplyitself is reachable and covered — this specific arm was the one gap.Added one test,
FleetMcpTest.sendTimesOutWithAnUnconfirmedNoteNotARetryInvitation: it drives anATTEMPTEDdelivery throughFleetMcp.send(same recipe asMessageServiceTest's ATTEMPTEDtest —
agentSendFailsWith+injector.onStatus(IDLE)), then asserts the returned text contains"delivery unconfirmed"and does not contain"retry or poll status"(the queued/workingarm's retry invitation). No production code changed —
formatReply'sTIMED_OUT_UNCONFIRMEDarmwas already correct; this only pins it.
Mutation proof (line-anchored
sedonFleetMcp.java'sTIMED_OUT_UNCONFIRMEDarm, swappingits text for the queued/working arm's text — the same mutation the lead ran by hand):
grep -Fxcon the arm's first line) count: 1.FleetMcpTest.sendTimesOutWithAnUnconfirmedNoteNotARetryInvitation:326 — got: [no reply within 150ms — worker unconfirmed; retry or poll status] ==> expected: <true> but was: <false>.shasum -a 256before mutation and after restore both:fcba00011880827ca4cdba881c533ba3f3d215aac4ca35bb23ef87684b1b0d93.Final build:
mvn -o clean install, profiledefault-excludes.[INFO] Results: Tests run: 1769, Failures: 0, Errors: 0, Skipped: 0—BUILD SUCCESS(1768 + this 1 new test).Already in
main— closing as merged.Head
d7239ed23is an ancestor oforigin/main, brought in by4507bc5a7("Merge worker/571-attempted-outcome-5739f7-2"). Merged locally and pushed, so the forge never
recorded it.
The
TIMED_OUT_UNCONFIRMEDthis PR added is live inMessageServiceat:1001-1004, on theInjector.Cancellation.ATTEMPTEDbranch. Related work I filed today: #588 shows that its siblingTIMED_OUT_WORKINGhas the opposite problem — it is confident, accurate about the clock, andsilently wrong about the worker.
Pull request closed